如何在ruby中将任何方法转换为中缀运算符 [英] How to convert any method to infix operator in 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)?
推荐答案
聚会有点晚了,但是我一直在玩弄它,您可以像 python (但还有更多工作要做),语法变为a |op| b
,方法如下:
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步:修复所有没有|
方法的类以及三种特殊情况(true
,false
和nil
)(注意:您可以在此处添加任何类,可能会很好地工作)
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>> b
,a -op- b
,a >op> b
和a <op<b
表示方向性,使用a **op** b
表示优先级以及您想要的任何其他组合,但是在使用true
,nil
作为具有逻辑运算符(|
,&&
,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屋!