kam*_*ber 2 ruby metaprogramming operators
在诸如Haskell的某些语言中,可以使用任何带有两个参数的函数作为中缀运算符。
我觉得这种表示法很有趣,并且希望在红宝石中也能达到同样的效果。
给定一个假想的方法or_if_familiar
,我想能够写类似"omg" or_if_familiar "oh!",而不是or_if_familiar("omg", "oh!")
如何在ruby中创建这样的符号(不修改ruby本身)?
派对晚了一点,但是我一直在玩弄它,并且您可以像python一样使用运算符重载来创建Infix运算符(但还有更多工作),语法变为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 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
#
Run Code Online (Sandbox Code Playgroud)
好
步骤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
Run Code Online (Sandbox Code Playgroud)
第2步:修复所有类没有一个|方法和三个特殊情况下(true,false,和nil)(注:您可以在这里添加任何类,它大概会做工精细)
[ 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}}
Run Code Online (Sandbox Code Playgroud)
步骤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
Run Code Online (Sandbox Code Playgroud)
步骤4:使用它们(间距无关紧要):
2 |pow| 3      # => 8
9|mod|4        # => 1
3| avg |6      # => 4.5
0 | pick | 1   # => 0 or 1 (randomly chosen)
Run Code Online (Sandbox Code Playgroud)
您甚至可以使用一点咖喱:(这仅适用于第一个操作数)
diff_from_3 = 3 |diff
diff_from_3| 2    # => 1
diff_from_3| 4    # => 1
diff_from_3| -3   # => 6
Run Code Online (Sandbox Code Playgroud)
另外,这个小方法可以让您无需使用.new以下命令即可定义Infix(或任何对象):
def Infix *args, &block
  Infix.new *args, &block
end
pow = Infix -> (x, y) {x ** y} # and so on
Run Code Online (Sandbox Code Playgroud)
剩下要做的就是将其包装在模块中
希望这对您有所帮助
PS可以渣土约与运营商有类似a <<op>> b,a -op- b,a >op> b和a <op<b用于方向性,a **op** b对于优先级和任何其他组合你想,但使用时要小心true,false并nil与逻辑运算符的第一个操作数(|,&&,not等),因为他们往往在调用infix运算符之前返回。
例如: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}
Run Code Online (Sandbox Code Playgroud)
        |   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           825 次  |  
        
|   最近记录:  |