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.
Ruby operator precedence table
Ruby 2.1.0, 2.0, 1.9, 1.8
An operator is a token that represents an operation (such as addition or comparison) to be performed on one or more operands. The operands are expressions, and operators allow us to combine these operand expressions into larger expressions. (Ref)
N = arity = The number of operands the operator operates on. (Ref)
A = associativity = The order of evaluation when the same operator (or operators with the same precedence) appear sequentially in an expression. The value L
means that expressions are evaluated from left to right. The value R
means that expressions are evaluated from right to left. And the value N
means that the operator is nonassociative and cannot be used multiple times in an expression without parentheses to specify the evaluation order. (Ref)
M = definability = Ruby implements a number of its operators as methods, allowing classes to define new meanings for those operators. Column M
of specifies which operators are methods. Operators marked with a Y
are implemented with methods and may be redefined, and operators marked with an N
may not. (Ref)
The following table is ordered according to descending precedence (highest precedence at the top).
N A M Operator(s) Description
- - - ----------- -----------
1 R Y ! ~ + boolean NOT, bitwise complement, unary plus
(unary plus may be redefined from Ruby 1.9 with +@)
2 R Y ** exponentiation
1 R Y - unary minus (redefine with -@)
2 L Y * / % multiplication, division, modulo (remainder)
2 L Y + - addition (or concatenation), subtraction
2 L Y << >> bitwise shift-left (or append), bitwise shift-right
2 L Y & bitwise AND
2 L Y | ^ bitwise OR, bitwise XOR (exclusive OR)
2 L Y < <= >= > ordering
2 N Y == === != =~ !~ <=> equality, pattern matching, comparison
(!= and !~ may not be redefined prior to Ruby 1.9)
2 L N && boolean AND
2 L N || boolean OR
2 N N .. ... range creation (inclusive and exclusive)
and boolean flip-flops
3 R N ? : ternary if-then-else (conditional)
2 L N rescue exception-handling modifier
2 R N = assignment
2 R N **= *= /= %= += -= assignment
2 R N <<= >>= assignment
2 R N &&= &= ||= |= ^= assignment
1 N N defined? test variable definition and type
1 R N not boolean NOT (low precedence)
2 L N and or boolean AND, boolean OR (low precedence)
2 N N if unless while until conditional and loop modifiers
What's the precedence of method calls with and without parentheses?
Prelude
This aims to test all possible scenarios.
Note that when saying "operator X
has higher precedence than method invocation" what is meant is in arguments. Aka:
invocation foo X bar
as opposed to (call on object)
X invocation
As far as the second case is concerned, method calls always have higher precedence.
Short answer
It doesn't fit:
- It causes
SyntaxError
in some cases - It has higher precedence than
rescue
, but lower than assignment
Summary
not
can't be used after method invocation regardless of brackets- Using brackets (
()
) with method invocations sometimes causes aSyntaxError
. These cases are:and
,or
,if
,unless
,until
,while
andrescue
- In cases when brackets don't cause an error, they don't change the precedence in any way
- All operators, except for
and
,or
, postfixif
,unless
,until
,while
,rescue
have higher precedence than method invocation
Lets try it:
class Noone < BasicObject
undef_method :!
def initialize(order)
@order = order
end
def method_missing(name, *args)
@order << name
self
end
end
First unary:
# + and - will become binary
unary_operators = %i(! ~ not defined?)
puts 'No brackets'
unary_operators.each do |operator|
puts operator
order = []
foo = Noone.new order
bar = Noone.new order
begin
eval("foo.meta #{operator} bar")
rescue SyntaxError => e
puts e
end
p order
puts '-----------'
end
puts 'Brackets'
unary_operators.each do |operator|
puts operator
order = []
foo = Noone.new order
bar = Noone.new order
begin
eval("foo.meta(#{operator} bar)")
rescue SyntaxError => e
puts e
end
p order
puts '-----------'
end
Points taken:
not
after a method invocation is aSyntaxError
- all unary operators have higher precedence than method invocation regardless of brackets
Now binary:
binary_operators = %i(
**
* / %
+ -
<< >>
&
| ^
> >= < <=
<=> == === =~
.. ...
or and
)
puts 'No brackets'
binary_operators.each do |operator|
order = []
foo = Noone.new order
bar = Noone.new order
baz = Noone.new order
begin
eval("foo.meta bar #{operator} baz")
rescue SyntaxError => e
puts e
end
p order
end
puts 'Brackets'
binary_operators.each do |operator|
order = []
foo = Noone.new order
bar = Noone.new order
baz = Noone.new order
begin
eval("foo.meta( bar #{operator} baz)")
rescue SyntaxError => e
puts e
end
p order
end
Points taken:
- brackets around method invocation with
and
oror
is aSyntaxError
- we have to test
and
andor
further without brackets ..
and...
call<=>
. We have to test this further- we couldn't test a few other binary operators this way, namely
&&
,||
,==
,!=
, modifierrescue
,if
,unless
,until
,while
- other than the above mentioned, operators have higher precedence, regardless of brackets
def yes
puts 'yes'
true
end
def no
puts 'no'
false
end
def anything(arg)
puts 'Anything'
arg
end
anything yes and no
anything no or yes
anything yes && no
anything no || yes
anything(yes && no)
anything(no || yes)
anything yes == no
anything(yes == no)
anything yes != no
anything(yes != no)
Points taken:
and
andor
have lower precedence without brackets&&
,||
,==
and!=
have higher precedence regardless of brackets
def five(*args)
p args
5
end
five 2..7
five(2..7)
five 2...7
five(2...7)
Points taken:
..
and...
have higher precedence regardless of brackets
anything yes if no
anything(yes if no)
anything no unless yes
anything(no unless yes)
anything no until yes
anything(no until yes)
anything yes while no
anything(yes while no)
Points taken:
- brackets with
if
,unless
,until
,while
cause aSyntaxError
- all of the above have lower precedence than method invocation without brackets
def error
puts 'Error'
raise
end
anything error rescue yes
anything(error rescue yes)
Points taken:
- brackets around
rescue
cause aSyntaxError
rescue
has lower precedence if no brackets are present
Ternary:
anything yes ? no : 42
anything(yes ? no : 42)
Points taken:
- ternary has higher precedence regardless of brackets
Assignment (left for last as it changes yes
and no
):
anything yes = no
anything(no = five(42))
Points taken:
- Assignment has higher precedence than invocation
Note that +=
and the like are just shortcuts for +
and =
so they exhibit the same behaviour.
Ruby self, operator precedence and instance method invocation
why
number
instead of@number
Because you have defined a reader/getter, might as well use it. Today method number
is backed by an instance variable, tomorrow it's computed (or lazily instantiated, etc.). By using the method and not its internals, you shield yourself from cascading changes. It's called "encapsulation". But you could have used the variable, it's just not a good practice.
the call to (self % i) doesn't require a space between the self and %, I'm guessing it has something to do with operator precedence?
No. Nothing to do with precedence. There was no ambiguity in the spaceless form and ruby was able to parse it successfully, that's why it's allowed.
Ruby operator method calls vs. normal method calls
The implementation doesn't have the additional complexity that would be needed to allow generic definition of new operators.
Instead, Ruby has a Yacc parser that uses a statically defined grammar. You get the built-in operators and that's it. Symbols occur in a fixed set of sentences in the grammar. As you have noted, the operators can be overloaded, which is more than most languages offer.
Certainly it's not because Matz was lazy.
Ruby actually has a fiendishly complex grammar that is roughly at the limit of what can be accomplished in Yacc. To get more complex would require using a less portable compiler generator or it would have required writing the parser by hand in C, and doing that would have limited future implementation portability in its own way as well as not providing the world with the Yacc input. That would be a problem because Ruby's Yacc source code is the only Ruby grammar documentation and is therefore "the standard".
Related Topics
How to Define a Simple Global Variable in an Rspec Test That Can Be Accesed by Helper Functions
Convert Ip Address to 32 Bit Integer in Ruby
Get All Keys in Hash with Same Value
Ruby on Rails: Creating a Model Entry with a Belongs_To Association
In Rspec, Using Let Variable Inside Before :All Block
Is There an Elegant Way to Exclude the First Value of a Range
How to Optimize Graphviz Output Width
How to Get Error Messages from Ruby Threads
How to Mock Super in Ruby Using Rspec
Exclude Option from Collection.Map in Ruby on Rails
What's Does the [5.0] in Rails 5's Activerecord::Migration Mean
How to "Extract" Values from a Multidimensional Array in a Smart Way
Ruby - Naming Convention - Letter Case for Acronyms in Class/Module Names
Rails 3 Caching: Expire Action for Named Route