如何将任何方法转换为ruby中的中缀运算符

在某些语言(如Haskell)中, 可以使用任何带有两个参数的函数作为中缀运算符。

我觉得这个符号很有意思,想在ruby中实现同样的目的。

假设有一个想象的方法or_if_familiar我希望能够写出类似"omg" or_if_familiar "oh!"东西"omg" or_if_familiar "oh!" 而不是or_if_familiar("omg", "oh!")

如何在ruby中创建这样的符号(不修改ruby本身)?

派对有点晚了但是我一直在玩它并且你可以使用运算符重载来创建Infix运算符就像在python中一样 (但是需要更多的工作),语法变成a |op| b a |op| b ,这是如何:

首先使用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 vo;Infix.new(@m,true,o)end end;[NilClass,FalseClass,TrueClass,Object,Array].each{|c|c.prepend Module.new{def|o;o.class==Infix ? ov(self):super end}};def Infix*a,&b;Infix.new *a,&b end # 

第1步:创建Infix

 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 

第2步:修复所有没有| 方法和三个特殊情况( truefalsenil )(注意:你可以在这里添加任何类,它可能会正常工作)

 [ 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}} 

第3步:以5种方式之一定义您的运算符

 # 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 

第4步:使用它们(间距无关紧要):

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

你甚至可以有点咖喱:(这只适用于第一个操作数)

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

作为奖励,这个小方法允许您在不使用.new情况下定义Infix(或任何对象):

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

剩下要做的就是将它包装在一个模块中

希望这有帮助

PS你可以通过运算符来解决类似a <> ba -op- ba >op> ba for a -op- ba **op** b for precedence和你想要的任何其他组合,但要注意使用truefalsenil作为第一个操作数与逻辑运算符( |&&not等),因为它们往往在调用中缀运算符之前返回。

例如: false |equivalent_of_or| 5 # => true false |equivalent_of_or| 5 # => true如果不正确,则为false |equivalent_of_or| 5 # => true

最后,运行它来检查所有内置类的大量情况作为第一个和第二个操作数:

 # 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} 

在Ruby中,运算符是前缀还是中缀是由解析器修复的。 运算符优先级也是固定的。 除了修改解析器之外,没有办法改变这些东西。

但是您可以为对象实现内置运算符

虽然您可能无法更改内置运算符的修正或优先级,但您可以通过定义方法为对象实现运算符。 这是因为Ruby将运算符转换为方法调用。 例如,这个表达式:

 a + b 

被翻译成:

 a.+(b) 

因此,您可以通过定义+方法为任意对象实现+运算符:

 def +(rhs) ... end 

前缀运算符-导致调用方法@- ,所以要实现前缀 – 你这样做:

 def @- .. end 

您也可以使用方法

您可以将自己的中缀运算符实现为普通方法。 这将需要与您想要的语法略有不同的语法。 你要:

 "omg" or_if_familiar "oh!" 

哪个你不能拥有。 你可以拥有的是:

 "omg".or_if_familiar "oh!" 

这是有效的,因为在Ruby中,方法参数的括号通常可以省略。 以上相当于:

 "omg".or_if_familiar("oh!") 

在这个例子中,我们将通过猴子修补String类来实现它:

 class String def or_ir_familiar(rhs) ... end end 

除了固定的和预定义的运算符集之外,Ruby没有中缀方法语法。 Ruby不允许用户代码更改语言语法。 因此,你想要的是不可能的。

根据Wayne Conrad的回答,我可以编写以下代码,这些代码适用于ruby顶级中定义的任何方法:

 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 

这允许写

 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 

谢谢Wayne!

关于同一主题的有趣参考:

  • 在Ruby中定义一个新的逻辑运算符