How to Convert Any Method to Infix Operator in Ruby

How to convert any method to infix operator in ruby

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:

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
#

Ok

Step 1: create the Infix class

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

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

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

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

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

Hope this helped

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.

For example: false |equivalent_of_or| 5 # => true if you don't correct.

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}

Define custom Ruby operator

Yes, custom operators can be created, although there are some caveats. Ruby itself doesn't directly support it, but the superators gem does a clever trick where it chains operators together. This allows you to create your own operators, with a few limitations:

$ gem install superators19

Then:

require 'superators19'

class Array
superator "%~" do |operand|
"#{self} percent-tilde #{operand}"
end
end

puts [1] %~ [2]
# Outputs: [1] percent-tilde [2]

Due to the aforementioned limitations, I couldn't do your 1 %! 2 example. The Documentation has full details, but Fixnums can't be given a superator, and ! can't be in a superator.

Passing an operator to a function?

Two possibilities:

Take method/operator name as a symbol:

def sum a,b,operator
a.send(operator, b)
end
sum 42, 23, :+

Or the more general solution: Take a block:

def sum a,b
yield a,b
end
sum 42, 23, &:+

Defining a new logical operator in Ruby

Without editing the Ruby parser and sources and compiling a new version of Ruby, you can't. If you want, you can use this ugly syntax:

class Object
def but(other)
self and other
end
end

x.but (not y)

Note that you can't remove the parentheses or the space in this snippet. It will also shadow the functionality of the code to someone else reading your code. Don't do it.

Is == a special method in Ruby?

No, == is not a special method in Ruby. It's a method like any other. What you are seeing is simply a parsing issue:

a.x foo 4

is the same as

a.x(foo(4))

IOW, you are passing foo(4) as an argument to x, but x doesn't take any arguments.

There is, however, special operator syntax, which allows you to write

a == b

instead of

a.== b

for a limited list of operators:

==
!=
<
>
<=
>=
<=>
===
&
|
*
/
+
-
%
**
>>
<<
!==
=~
!~

Also, there is special syntax that allows you to write

!a

and

~a

instead of

a.!

and

a.~

As well as

+a

and

-a

instead of

a.+@

and

a.-@

Then, there is

a[b]

and

a[b] = c

instead of

a.[] b

and

a.[]= b, c

and last but not least

a.(b)

instead of

a.call b

how can i rename a operator method with words, respecting sintax in ruby?

Ruby has particular operators you can override, like %, +, and &, but you can't just invent arbitrary operators on a whim. You need to work with pre-existing ones.

This is a function of how the Ruby parser works. It can only identify a pre-defined set of symbols outside of regular method calls.

Trait.new.with x is a method call, equivalent to Trait.new.send(:with, x), while Trait.new with x is Trait.new(with(x)) which is not what you want.

Your alias creates a method, it does not create an operator. You cannot create a brand-new operator.

You're going to have to decide between the two forms x & y vs. x.with y.

If statement operator as a variable in ruby

Try this one

x = 0
y = 1
z = "!="

x.public_send(z, y)
=> true

The trick here is know that 3 >= 5 is syntactic sugar for 3.>=(5)

Convert postgres hstore sql to Arel

You can accomplish this with Arel by creating your own Arel::Nodes::InfixOperation:

ut = Arel::Table.new(:user)
hstore_key = Arel::Nodes::InfixOperation.new("->", ut[:properties], 'is_robber')
User.where(hstore_key.eq('true'))

Will produce:

SELECT "users".* FROM "users" WHERE "users"."properties" -> 'is_robber' = 'true'

If you've never heard of infix notation, Wikipedia gives a good explaination:

Infix notation is the common arithmetic and logical formula notation, in which operators are written infix-style between the operands they act on (e.g. 2 + 2). It is not as simple to parse by computers as prefix notation ( e.g. + 2 2 ) or postfix notation ( e.g. 2 2 + ), but many programming languages use it due to its familiarity.

Unfortunately, there's not much documentation for Arel, and none for the InfixOperation node, so it can be frustrating to start out with. When you're looking for how to perform particular SQL operations with Arel, your best bet is to look through the nodes directory in the source code.



Related Topics



Leave a reply



Submit