How to convert any method to infix operator in ruby

后端 未结 4 1305
陌清茗
陌清茗 2020-12-20 03:40

In some language such as Haskell, it is possible to use any function taking two arguments as an infix operator.

I find this notation interesting and would like to ac

相关标签:
4条回答
  • 2020-12-20 04:20

    In Ruby, whether the operator is prefix or infix is fixed by the parser. Operator precedence is also fixed. There is no way, short of modifying the parser, of changing these things.

    But you can implement the built-in operators for your objects

    Although you may not change the fix-ness or precedence of a built-in operator, you may implement operators for your objects by defining methods. That is because Ruby translates operators into method calls. For example, this expression:

    a + b
    

    is translated into:

    a.+(b)
    

    Therefore, you may implement the + operator for an arbitrary object by defining the + method:

    def +(rhs)
      ...
    end
    

    The prefix operator - causes a call to method @-, so to implement prefix - you do this:

    def @-
      ..
    end
    

    You may also use methods

    You may implement your own infix operators as plain methods. This will require a slightly different syntax than what you want. You want:

    "omg" or_if_familiar "oh!"
    

    Which you cannot have. What you can have is:

    "omg".or_if_familiar "oh!"
    

    This works because, in Ruby, the parentheses on method arguments may often be omitted. The above is equivalent to:

    "omg".or_if_familiar("oh!")
    

    In this example, we would implement this by monkey-patching the String class:

    class String
      def or_ir_familiar(rhs)
        ...
      end
    end
    
    0 讨论(0)
  • 2020-12-20 04:27

    Ruby does not have infix method syntax, except for a fixed and predefined set of operators. And Ruby does not allow user code to change the language syntax. Ergo, what you want is not possible.

    0 讨论(0)
  • 2020-12-20 04:41

    A bit late to the party but I've been toying around with it and you can use operator overloading to create Infix operators just like in python (but with a bit more work), the syntax becomes a |op| b, here's how:

    First a quick and dirty copy-paste to play around with Infix:

    class Infix def initialize*a,&b;raise'arguments size mismatch'if a.length<0||a.length>3;raise'both method and b passed'if a.length!=0&&b;raise'no arguments passed'if a.length==0&&!b;@m=a.length>0? a[0].class==Symbol ? method(a[0]):a[0]:b;if a.length==3;@c=a[1];@s=a[2]end end;def|o;if@c;o.class==Infix ? self:@m.(@s,o)else;raise'missing first operand'end end;def coerce o;[Infix.new(@m,true,o),self]end;def v o;Infix.new(@m,true,o)end end;[NilClass,FalseClass,TrueClass,Object,Array].each{|c|c.prepend Module.new{def|o;o.class==Infix ? o.v(self):super end}};def Infix*a,&b;Infix.new *a,&b end
    #
    

    Ok

    Step 1: create the Infix class

    class Infix
      def initialize *args, &block
        raise 'error: arguments size mismatch' if args.length < 0 or args.length > 3
        raise 'error: both method and block passed' if args.length != 0 and block
        raise 'error: no arguments passed' if args.length == 0 and not block
        @method = args.length > 0 ? args[0].class == Symbol ? method(args[0]) : args[0] : block
        if args.length == 3; @coerced = args[1]; @stored_operand = args[2] end
      end
      def | other
        if @coerced
          other.class == Infix ? self : @method.call(@stored_operand, other)
        else
          raise 'error: missing first operand'
        end
      end
      def coerce other
        [Infix.new(@method, true, other), self]
      end
      def convert other
        Infix.new(@method, true, other)
      end
    end
    

    Step 2: fix all the classes that don't have a | method and the three special cases (true, false, and nil) (note: you can add any class in here and it will probably work fine)

    [ NilClass, FalseClass, TrueClass,
      Float, Symbol, String, Rational,
      Complex, Hash, Array, Range, Regexp
    ].each {|c| c.prepend Module.new {
      def | other
        other.class == Infix ? other.convert(self) : super
      end}}
    

    Step 3: define your operators in one of 5 ways

    # Lambda
    pow = Infix.new -> (x, y) {x ** y}
    # Block
    mod = Infix.new {|x, y| x % y}
    # Proc
    avg = Infix.new Proc.new {|x, y| (x + y) / 2.0}
    # Defining a method on the spot (the method stays)
    pick = Infix.new def pick_method x, y
      [x, y][rand 2]
    end
    # Based on an existing method
    def diff_method x, y
      (x - y).abs
    end
    diff = Infix.new :diff_method
    

    Step 4: use them (spacing doesn't matter):

    2 |pow| 3      # => 8
    9|mod|4        # => 1
    3| avg |6      # => 4.5
    0 | pick | 1   # => 0 or 1 (randomly chosen)
    

    You can even kinda sorta curry: (This only works with the first operand)

    diff_from_3 = 3 |diff
    
    diff_from_3| 2    # => 1
    diff_from_3| 4    # => 1
    diff_from_3| -3   # => 6
    

    As a bonus, this little method allows you to define Infixes (or any object really) without using .new:

    def Infix *args, &block
      Infix.new *args, &block
    end
    
    pow = Infix -> (x, y) {x ** y} # and so on
    

    All that's left to do is wrap it up in a module

    Hope this helped

    P.S. You can muck about with the operators to have something like a <<op>> b, a -op- b, a >op> b and a <op<b for directionality, a **op** b for precedence and any other combination you want but beware when using true, false and nil as the first operand with logical operators (|, &&, not, etc.) as they tend to return before the infix operator is called.

    For example: false |equivalent_of_or| 5 # => true if you don't correct.

    FINALLY, run this to check a bunch of cases of all the builtin classes as both the first and second operand:

    # pp prints both inputs
    pp = Infix -> (x, y) {"x: #{x}\ny: #{y}\n\n"}
    
    [ true, false, nil, 0, 3, -5, 1.5, -3.7, :e, :'3%4s', 'to',
      /no/, /(?: [^A-g7-9]\s)(\w{2,3})*?/,
      Rational(3), Rational(-9.5), Complex(1), Complex(0.2, -4.6),
      {}, {e: 4, :u => 'h', 12 => [2, 3]},
      [], [5, 't', :o, 2.2, -Rational(3)], (1..2), (7...9)
    ].each {|i| puts i.class; puts i |pp| i}
    
    0 讨论(0)
  • 2020-12-20 04:44

    Based on Wayne Conrad's answer, I can write the following code that would work for any method defined in ruby top-level:

    class Object
      def method_missing(method, *args)
        return super if args.size != 1
        # only work if "method" method is defined in ruby top level
        self.send(method, self, *args)
      end
    end
    

    which allows to write

    def much_greater_than(a,b)
      a >= b * 10
    end
    
    "A very long sentence that say nothing really but should be long enough".much_greater_than "blah"
    
    # or
    
    42.much_greater_than 2
    

    Thanks Wayne!

    Interesting reference on the same subject:

    • Defining a new logical operator in Ruby
    0 讨论(0)
提交回复
热议问题