【问题标题】:How to convert any method to infix operator in ruby如何将任何方法转换为 ruby​​ 中的中缀运算符
【发布时间】:2017-04-14 19:15:39
【问题描述】:

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

我觉得这个符号很有趣,并希望在 ruby​​ 中实现同样的效果。

给定一个假想的方法or_if_familiar 我希望能够写类似"omg" or_if_familiar "oh!" 而不是or_if_familiar("omg", "oh!")

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

【问题讨论】:

  • 我找不到任何 ruby​​ 示例,但我承认我还没有尝试编写任何代码
  • 因为这是解析器的一个特性,所以不可能。

标签: ruby operators metaprogramming


【解决方案1】:

在 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

【讨论】:

    【解决方案2】:

    Ruby 没有中缀方法语法,除了一组固定和预定义的运算符。并且 Ruby 不允许用户代码更改语言语法。所以,你想要的都是不可能的。

    【讨论】:

      【解决方案3】:

      根据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
      

      谢谢韦恩!

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

      【讨论】:

        【解决方案4】:

        聚会有点晚了,但我一直在玩弄它,您可以使用运算符重载来创建中缀运算符,就像在python 中一样(但需要做更多工作),语法变为a |op| b,方法如下:

        首先使用中缀进行快速而肮脏的复制粘贴:

        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
        #
        

        好的

        第 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 的情况下定义中缀(或任何对象):

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

        剩下要做的就是把它封装在一个模块中

        希望这有帮助

        附:您可以与操作员一起使用 a &lt;&lt;op&gt;&gt; ba -op- ba &gt;op&gt; ba &lt;op&lt;b 来表示方向性,a **op** b 表示优先级以及您想要的任何其他组合,但在使用 true、@987654342 时要小心@ 和 nil 作为带有逻辑运算符(|&amp;&amp;not 等)的第一个操作数,因为它们往往会在调用中缀运算符之前返回。

        例如:false |equivalent_of_or| 5 # =&gt; 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}
        

        【讨论】:

        • 这既美丽又恐怖。
        猜你喜欢
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        • 2018-07-13
        • 1970-01-01
        • 2015-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多