Operators
Remarks#
Operators are methods
Most operators are actually just methods, so x + y
is calling the +
method of x
with argument y
, which would be written x.+(y)
. If you write a method of your own having semantic meaning of a given operator, you can implement your variant in the class.
As a silly example:
# A class that lets you operate on numbers by name.
class NamedInteger
name_to_value = { 'one' => 1, 'two' => 2, ... }
# define the plus method
def + (left_addend, right_addend)
name_to_value(left_addend) + name_to_value(right_addend)
end
...
end
When to use &&
vs. and
, ||
vs. or
Note that there are two ways to express booleans, either &&
or and
, and ||
or or
— they are often interchangeable, but not always. We’ll refer to these as “character” and “word” variants.
The character variants have higher precedence so reduce the need for parentheses in more complex statements helps avoid unexpected errors.
The word variants were originally intended as control flow operators rather than boolean operators. That is, they were designed to be used in chained method statements:
raise 'an error' and return
While they can be used as boolean operators, their lower precedence makes them unpredictable.
Secondly, many rubyists prefer the character variant when creating a boolean expression (one that evaluates to true
or false
) such as x.nil? || x.empty?
. On the other hand, the word variants are preferred in cases where a series of methods are being evaluated, and one may fail. For example a common idiom using the word variant for methods that return nil
on failure might look like:
def deliver_email
# If the first fails, try the backup, and if that works, all good
deliver_by_primary or deliver_by_backup and return
# error handling code
end
Operator Precedence and Methods
From highest to lowest, this is the precedence table for Ruby. High precedence operations happen before low precedence operations.
╔═══════════════════════╦════════════════════════════════════════╦═════════╗
║ Operators ║ Operations ║ Method? ║
╠═══════════════════════╬════════════════════════════════════════╬═════════╣
║ . ║ Method call (e.g. foo.bar) ║ ║
║ [] []= ║ Bracket Lookup, Bracket Set ║ ✓¹ ║
║ ! ~ + ║ Boolean NOT, complement, unary plus ║ ✓² ║
║ ** ║ Exponentiation ║ ✓ ║
║ - ║ Unary minus ║ ✓² ║
║ * / % ║ Multiplication, division, modulo ║ ✓ ║
║ + - ║ Addition, subtraction ║ ✓ ║
║ << >> ║ Bitwise shift ║ ✓ ║
║ & ║ Bitwise AND ║ ✓ ║
║ | ^ ║ Bitwise OR, Bitwise XOR ║ ✓ ║
║ < <= >= > ║ Comparison ║ ✓ ║
║ <=> == != === =~ !~ ║ Equality, pattern matching, comparison ║ ✓³ ║
║ && ║ Boolean AND ║ ║
║ || ║ Boolean OR ║ ║
║ .. ... ║ Inclusive range, Exclusive range ║ ║
║ ? : ║ Ternary operator ║ ║
║ rescue ║ Modifier rescue ║ ║
║ = += -= ║ Assignments ║ ║
║ defined? ║ Defined operator ║ ║
║ not ║ Boolean NOT ║ ║
║ or and ║ Boolean OR, Boolean AND ║ ║
║ if unless while until ║ Modifier if, unless, while, until ║ ║
║ { } ║ Block with braces ║ ║
║ do end ║ Block with do end ║ ║
╚═══════════════════════╩════════════════════════════════════════╩═════════╝
Unary + and unary - are for +obj
, -obj
or -(some_expression)
.
Modifier-if, modifier-unless, etc. are for the modifier versions of those keywords. For example, this is a modifier-unless expression:
a += 1 unless a.zero?
Operators with a ✓ may be defined as methods. Most methods are named exactly as the operator is named, for example:
class Foo
def **(x)
puts "Raising to the power of #{x}"
end
def <<(y)
puts "Shifting left by #{y}"
end
def !
puts "Boolean negation"
end
end
Foo.new ** 2 #=> "Raising to the power of 2"
Foo.new << 3 #=> "Shifting left by 3"
!Foo.new #=> "Boolean negation"
¹ The Bracket Lookup and Bracket Set methods ([]
and []=
) have their arguments defined after the name, for example:
class Foo
def [](x)
puts "Looking up item #{x}"
end
def []=(x,y)
puts "Setting item #{x} to #{y}"
end
end
f = Foo.new
f[:cats] = 42 #=> "Setting item cats to 42"
f[17] #=> "Looking up item 17"
² The “unary plus” and “unary minus” operators are defined as methods named +@
and -@
, for example
class Foo
def -@
puts "unary minus"
end
def +@
puts "unary plus"
end
end
f = Foo.new
+f #=> "unary plus"
-f #=> "unary minus"
³ In early versions of Ruby the inequality operator !=
and the non-matching operator !~
could not be defined as methods.
Instead, the method for the corresponding equality operator ==
or matching operator =~
was invoked, and the result of that
method was boolean inverted by Ruby.
If you do not define your own !=
or !~
operators the above behavior is still true. However, as of Ruby 1.9.1, those two operators may also be
defined as methods:
class Foo
def ==(x)
puts "checking for EQUALITY with #{x}, returning false"
false
end
end
f = Foo.new
x = (f == 42) #=> "checking for EQUALITY with 42, returning false"
puts x #=> "false"
x = (f != 42) #=> "checking for EQUALITY with 42, returning false"
puts x #=> "true"
class Foo
def !=(x)
puts "Checking for INequality with #{x}"
end
end
f != 42 #=> "checking for INequality with 42"
Case equality operator (===)
Also known as triple equals.
This operator does not test equality, but rather tests if the right operand has an IS A relationship with the left operand. As such, the popular name case equality operator is misleading.
This SO answer describes it thus: the best way to describe a === b
is “if I have a drawer labeled a
, does it make sense to put b
in it?” In other words, does the set a
include the member b
?
Examples (source)
(1..5) === 3 # => true
(1..5) === 6 # => false
Integer === 42 # => true
Integer === 'fourtytwo' # => false
/ell/ === 'Hello' # => true
/ell/ === 'Foobar' # => false
Classes that override ===
Many classes override ===
to provide meaningful semantics in case statements. Some of them are:
╔═════════════════╦════════════════════╗
║ Class ║ Synonym for ║
╠═════════════════╬════════════════════╣
║ Array ║ == ║
║ ║ ║
║ Date ║ == ║
║ ║ ║
║ Module ║ is_a? ║
║ ║ ║
║ Object ║ == ║
║ ║ ║
║ Range ║ include? ║
║ ║ ║
║ Regexp ║ =~ ║
║ ║ ║
║ String ║ == ║
╚═════════════════╩════════════════════╝
Recommended practice
Explicit use of the case equality operator ===
should be avoided. It doesn’t test equality but rather subsumption, and its use can be confusing. Code is clearer and easier to understand when the synonym method is used instead.
# Bad
Integer === 42
(1..5) === 3
/ell/ === 'Hello'
# Good, uses synonym method
42.is_a?(Integer)
(1..5).include?(3)
/ell/ =~ 'Hello'
Safe Navigation Operator
Ruby 2.3.0 added the safe navigation operator, &.
. This operator is intended to shorten the paradigm of object && object.property && object.property.method
in conditional statements.
For example, you have a House
object with an address
property, and you want to find the street_name
from the address
. To program this safely to avoid nil errors in older Ruby versions, you’d use code something like this:
if house && house.address && house.address.street_name
house.address.street_name
end
The safe navigation operator shortens this condition. Instead, you can write:
if house&.address&.street_name
house.address.street_name
end
Caution:
The safe navigation operator doesn’t have exactly the same behavior as the chained conditional. Using the chained conditional (first example), the if
block would not be executed if, say address
was false
. The safe navigation operator only recognises nil
values, but permits values such as false
. If address
is false
, using the SNO will yield an error:
house&.address&.street_name
# => undefined method `address' for false:FalseClass