如何在ruby中将任何方法转换为中缀运算符 [英] How to convert any method to infix operator in ruby

查看:78
本文介绍了如何在ruby中将任何方法转换为中缀运算符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 achieve the same in ruby.

给出一种虚构的方法or_if_familiar 我希望能够写类似"omg" or_if_familiar "oh!"而不是or_if_familiar("omg", "oh!")

Given a imaginary method or_if_familiar I'd like to be able to write something like "omg" or_if_familiar "oh!" instead of or_if_familiar("omg", "oh!")

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

How one would create such a notation in ruby (without modifying ruby itself)?

推荐答案

聚会有点晚了,但是我一直在玩弄它,您可以像

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:

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

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
#

步骤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)(注意:您可以在此处添加任何类,可能会很好地工作)

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

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

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

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

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

作为奖励,此小方法使您无需使用.new即可定义中缀(或实际上是任何对象):

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

希望这对您有帮助

P.S.您可以与运算符混为一谈,例如使用a <<op>> ba -op- ba >op> ba <op<b表示方向性,使用a **op** b表示优先级以及您想要的任何其他组合,但是在使用truenil作为具有逻辑运算符(|&&not等)的第一个操作数,因为它们倾向于在调用infix运算符之前返回.

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.

例如:false |equivalent_of_or| 5 # => true如果您不正确.

最后,运行此命令以检查所有内置类作为第一操作数和第二操作数的一堆情况:

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}

这篇关于如何在ruby中将任何方法转换为中缀运算符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆