Define Custom Ruby Operator

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.

Defining custom operators in ruby

Yes, you can. For example:

class Fixnum
def **(x)
self.*(x)*1.0
end
end

5**4 #==> 20.0

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}

Ruby operator and method invoking

This is one of the things many people refer to as Ruby's syntax sugar. The language recognizes these mathematical operators as well as other ASCII symbol operators, such as the << insertion operator, and allows them to be called with white space and without the method dot. This allows for a more natural usage of the method.

Your #add method is not recognized by Ruby to be a standard operator and so this syntax help is not performed.

Overloading :+= in Ruby

Unlike C++ or other languages with robust overrides, there's no such method as += in Ruby.

What's happening internally is a sort of expansion. For example:

x += n

If x is a variable, then this is equivalent to:

x = x.send(:+, n)

If x= is defined, then this is equivalent to:

send(:x=, x.send(:+, n))

Whatever you need to do to override must be to redefine x= or + on the class of x.

Remember that the + method should not modify x. It's supposed to return a copy of x with the modification applied. It should also return an object of the same class of x for consistency.

Your method should look like:

def +(other)
# Create a new object that's identical in class, passing in any arguments
# to the constructor to facilitate this.
result = self.class.new(...)

result.value += other.to+f

result
end

Is it possible to use a custom equality operator with Ruby's Set?

According to Ruby Set class: equality of sets, you need to override both Object#eql? and Object#hash

Calling custom method from within a Ruby core class

When you define a method on the top level, it is added an an instance method on Object and therefore accessible to descendent classes (most of the other core classes)

def foo
1
end
method(:foo)
# => #<Method: Object#foo>

However the access level of this method seems to be different in IRB/pry than when running a script.

In IRB:

puts [].foo
# => 1

In a script:

puts [].foo
# => NoMethodError (private method called...)

Of course, you can always just call the private method using send:

[].send(:foo)
# or, in your case, f.send(:puts_content, text_generated, some_condition?)

Also, in neither case will it overwrite the method on the descendant class if it was already defined:

def length
1
end
puts [].length
# => 0

Your second approach (patching the core class directly) will work and will overwrite the puts_content if it was already defined on File (which it wasn't). However if you want to avoid patching core classes, there are two approaches I recommend:

  1. Use a static (class) method and pass the file object as an argument

    class FileUtils
    def self.puts_content(file, text, cond)
    file.puts "Text to be inserted"
    file.puts text
    file.puts "Some other text" if cond
    file.puts ""
    end
    end
    File.open("file.txt", "w").each do |f|
    FileUtils.puts_content(f, text_generated, some_condition?)
    end
  2. use a refinement:

    module FileUtils
    refine File do
    def puts_content(text, cond)
    puts "Text to be inserted"
    puts text
    puts "Some other text" if cond
    puts ""
    end
    end
    end

    # elsewhere ...
    using FileUtils

    File.open("file.txt", "w").each do |f|
    f.puts_content(f, text_generated, some_condition?)
    end

    You can read about refinements here, but essentially, they're a way to patch a core class in a certain file or class only. This gives you the benefit of that nice, terse monkey-patched syntax with less risk of changing the behavior defined elsewhere.

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.



Related Topics



Leave a reply



Submit