An operator is a token
in the Ruby language 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. The numeric literal 2
and the operator +
, for example, can be combined into the
expression 2+2
. And the following
expression combines a numeric literal, a method invocation expression,
and a variable reference expression with the multiplication operator and
the less-than operator:
2 * Math.sqrt(2) < limit
Table 4-2 later in this section summarizes each of Ruby’s operators, and the sections that follow describe each one in detail. To fully understand operators, however, you need to understand operator arity, precedence, and associativity.
The arity of an operator is the number of
operands it operates on. Unary operators expect a single operand. Binary
operators expect two operands. Ternary operators (there is only one of these) expect three operands. The arity of each
operator is listed in column N of Table 4-2. Note
that the operators +
and –
have both unary and binary forms.
The precedence of an operator specifies how “tightly” an operator is bound to its operands, and affects the order of evaluation of an expression. Consider this expression, for example:
1 + 2 * 3 # => 7
The multiplication operator has higher precedence than the addition operator, so the multiplication is performed first and the expression evaluates to 7. Table 4-2 is arranged in order from high-precedence operators to low-precedence operators. Note that there are both high- and low-precedence operators for Boolean AND, OR, and NOT operations.
Operator precedence only specifies the default order of evaluation for an expression. You can always use parentheses to group subexpressions and specify your own order of evaluation. For example:
(1 + 2) * 3 # => 9
The associativity of an operator specifies the order of evaluation when the same operator (or operators with the same precedence) appear sequentially in an expression. Column A of Table 4-2 specifies the associativity of each operator. 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.
Most arithmetic operators are left-associative, which means that
10-5-2
is evaluated as (10-5)-2
instead of 10-(5-2)
. Exponentiation, on the other hand,
is right-associative, so 2**3**4
is
evaluated as 2**(3**4)
. Assignment is
another right-associative operator. In the expression a=b=0
, the value 0
is first assigned to the variable b
. Then the value of that expression (also
0
) is assigned to the variable
a
.
Ruby implements a number of its operators as methods, allowing
classes to define new meanings for those operators. Column M of Table 4-2 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. In general, classes may define their own
arithmetic, ordering, and equality operators, but they may not redefine
the various Boolean operators. We categorize operators in this chapter
according to their most common purpose for the standard Ruby classes.
Other classes may define different meanings for the operators. The
+
operator, for example, performs
numeric addition and is categorized as an arithmetic operator. But it is
also used to concatenate strings and arrays. A method-based operator is
invoked as a method of its lefthand operand (or its only operand, in the
case of unary operators). The righthand operand is passed as an argument
to the method. You can look up a class’s definition of any method-based
operator as you would look up any other method of a class. For example,
use ri to look up the definition of the *
operator for strings:
ri 'String.*'
To define unary +
and unary
–
operators, use method names
+@
and -@
to avoid ambiguity with the binary
operators that use the same symbols. The !=
and !~
operators are defined as the negation of the ==
and =~
operators. In Ruby 1.9, you can redefine !=
and
!~
. In earlier versions of the
language, you cannot. Ruby 1.9 also allows the unary !
operator to be redefined.
Table 4-2. Ruby operators, by precedence (high to low), with arity (N), associativity (A), and definability (M)
Operator(s) | N | A | M | Operation |
---|---|---|---|---|
! ~ + | 1 | R | Y | Boolean NOT, bitwise complement, unary plus[a] |
** | 2 | R | Y | Exponentiation |
- | 1 | R | Y | Unary minus (define 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 |
< <= >=
> | 2 | L | Y | Ordering |
== === != =~ !~
<=> | 2 | N | Y | Equality, pattern matching, comparison[b] |
&& | 2 | L | N | Boolean AND |
|| | 2 | L | N | Boolean OR |
.. ... | 2 | N | N | Range creation and Boolean flip-flops |
?: | 3 | R | N | Conditional |
rescue | 2 | L | N | Exception-handling modifier |
= **= *= /= %= += -= <<= >>= &&= &= ||= |= ^= | 2 | R | N | Assignment |
defined? | 1 | N | N | Test variable definition and type |
not | 1 | R | N | Boolean NOT (low precedence) |
and or | 2 | L | N | Boolean AND, Boolean OR (low precedence) |
if unless while
until | 2 | N | N | Conditional and loop modifiers |
[a] [b] |
The unary minus operator changes the sign of its numeric argument.
The unary plus is allowed, but it has no effect on numeric operands—it
simply returns the value of its operand. It is provided for symmetry
with unary minus, and can, of course, be redefined. Note that unary
minus has slightly lower precedence than unary plus; this is described
in the next section on the **
operator.
The names of these unary operators as methods are -@
and +@
. Use these names when redefining the
operators, invoking the operators as methods, or looking up
documentation for the operators. These special names are necessary to
disambiguate the unary plus and minus operators from binary plus and
minus.
**
performs exponentiation,
raising its first argument to the power of the second.
Note that you can compute roots of a number by using a fractional
number as the second operand. For example, the cube root of x
is x**(1.0/3.0)
. Similarly, x**-y
is the same as 1/(x**y)
. The **
operator is right-associative, so
x**y**z
is the same thing as
x**(y**z)
.
Finally, note that **
has higher
precedence than the unary minus operator, so -1**0.5
is the same thing as -(1**0.5)
. If you really want to take the
square root of -1
, you must use
parentheses: (-1)**0.5
. (The
imaginary result is not-a-number, and the expression evaluates to
NaN
.)
The operators +
, –
, *
, and
/
perform addition, subtraction, multiplication, and division on all
Numeric
classes. Integer division
returns an integer result and discards any remainder. The remainder
can be computed with the modulo operator %
. Integer division by zero raises ZeroDivisionError
. Floating-point division
by zero returns plus or minus Infinity
. Floating-point division of zero by
zero returns NaN
. See Arithmetic in Ruby for further details on Ruby’s integer and
floating-point arithmetic.
The String
class uses the
+
operator for string
concatenation, the *
operator for
string repetition, and the %
operator for sprintf
argument
substitution into a string.
The Array
class uses +
for array concatenation and –
for array subtraction. Array
uses the *
operator in different ways, depending on
the class of the second operand. When an array is “multiplied” by a
number, the result is a new array that repeats the contents of the
operand array the specified number of times. But when an array is
multiplied by a string, the result is the same as calling the join
method of the array and passing that
string as the argument.
The Fixnum
and Bignum
classes define the <<
and >>
operators to shift the
bits of the lefthand argument to the left and to the right. The
righthand argument is the number of positions to shift the bits, and
negative values result in a shift in the opposite direction: a
left-shift of –2
is the same as a
right-shift of 2
. High-order bits
are never “shifted off” when a Fixnum
is shifted left. If the result of a
shift does not fit in a Fixnum
, a
Bignum
value is returned. Right
shifts, however, always discard the low-order bits of the
argument.
Shifting a number left by 1
bit is the same as multiplication by 2
. Shifting a number right by 1
bit is the same as integer division by
2
. Here are some examples that
express numbers in binary notation and then convert their results back
to binary form:
(0b1011 << 1).to_s(2) # => "10110" 11 << 1 => 22 (0b10110 >> 2).to_s(2) # => "101" 22 >> 2 => 5
The <<
operator is also
used as an append operator, and it’s probably more common in this
form. The String
, Array
, and IO
classes define it in this way, as do a
number of other “appendable” classes from the standard library, such
as Queue
and Logger
:
message = "hello" # A string messages = [] # An empty array message << " world" # Append to the string messages << message # Append message to the array STDOUT << message # Print the message to standard output stream
Fixnum
and Bignum
define these operators to perform
bitwise NOT, AND, OR, and XOR operations. ~
is a high-precedence unary operator, and the others are medium-precedence binary operators.
~
changes each 0
bit of its integer operand to a 1
, and each 1
bit to a 0
, producing the binary 1
s-complement of a number. For any integer
x
, ~x
is the same as -x-1
.
&
is the bitwise AND
operator for two numbers. The bits of the result are set to 1
only if the corresponding bit in each
operand is set to 1
. For
example:
(0b1010 & 0b1100).to_s(2) # => "1000"
|
is the bitwise OR operator
for two integers. A bit in the result is 1
if either corresponding bit in the
operands is 1
. For example:
(0b1010 | 0b1100).to_s(2) # => "1110"
^
is the bitwise XOR
(exclusive-OR) for integers. A bit in the result is 1
if one (but not both) of the corresponding
bits in the operands is 1
. For
example:
(0b1010 ^ 0b1100).to_s(2) # => "110"
Other classes use these operators as well, usually in ways that
are compatible with their logical AND, OR, and NOT meanings. Arrays use &
and |
for set intersection and union operations.
When &
is applied to two
arrays, it returns a new array that contains only those elements that
appear in the lefthand array AND the righthand array. When |
is applied to two arrays, it returns a new
array that contains any elements that appear in either the lefthand
array OR the righthand array. See Arrays as sets for
details and examples.
TrueClass
, FalseClass
, and NilClass
define &
, |
,
and ^
(but not ~
), so that they can be used as Boolean
operators. Note, however, that this is rarely the correct thing to do.
The Boolean operators &&
and ||
(described later in Boolean Operators: &&, ||, !, and, or, not) are intended for Boolean operands, and
are more efficient because they do not evaluate their righthand
operand unless its value will affect the result of the
operation.
Some classes define a natural order for their values. Numbers
are ordered by magnitude; strings are ordered alphabetically; dates
are ordered chronologically. The less-than (<
), less-than-or-equal-to (<=
), greater-than-or-equal-to (>=
), and greater-than (>
) operators make assertions about the
relative order of two values. They evaluate to true
if the assertion is true, and they
evaluate to false
otherwise. (And
they typically raise an exception if their arguments are of
incomparable types.)
Classes may define the comparison operators individually. It is
easier and more common, however, for a class to define the single
<=>
operator. This is a
general-purpose comparison operator, and its return value indicates
the relative order of the two operands. If the lefthand operand is
less than the righthand operand, then <=>
returns –1
. If the lefthand operand is greater, it
returns +1
. If the two operands are
equal, the operator returns 0
. And
if the two operands are not comparable, it returns nil
.[*] Once the <=>
operator is defined, a class may simply include the module Comparable
,
which defines the other
comparison operators (including the ==
operator) in terms of <=>
.
The Module
class deserves special mention: it implements the comparison
operators to indicate subclass relationships (Module
is the superclass of Class
). For classes A
and B
,
A < B
is true
if A
is a subclass or descendant of B
.
In this case, “less than” means “is more specialized than” or “is a
narrower type than.” As a mnemonic, note that (as we’ll learn in Chapter 7) the <
character is also used when declaring a subclass:
# Declare class A as a subclass of B class A < B end
Module
defines >
to work like <
with its operands reversed. And it
defines <=
and >=
so that they also return true
if the two operands are the same class.
The most interesting things about these Module
comparison operators is that Module
only defines a partial ordering on
its values. Consider the classes String
and Numeric
. Both are subclasses of Object
, and neither one is a subclass of the
other. In this case, when the two operands are unrelated, the
comparison operators return nil
instead of true
or false
:
String < Object # true: String is more specialized than Object Object > Numeric # true: Object is more general than Numeric Numeric < Integer # false: Numeric is not more specialized than Integer String < Numeric # nil: String and Numeric are not related
If a class defines a total ordering on its values, and a < b
is not true, then you can be sure
that a >= b
is true. But when a class, like Module
, defines only a partial ordering, you
must not make this assumption.
==
is the equality operator. It determines whether two values
are equal, according to the lefthand operand’s definition of “equal.”
The !=
operator is simply the
inverse of ==
: it calls ==
and then returns the opposite. You can
redefine !=
in Ruby 1.9 but not in
Ruby 1.8. See Object Equality for a more detailed
discussion of object equality in Ruby.
=~
is the pattern-matching
operator. Object
defines this
operator so that it always returns false
. String redefines it so that it
expects a Regexp
as its righthand
argument. And Regexp
redefines the
operator so that it expects a String
as its righthand argument. Both of
these operators return nil
if the
string does not match the pattern. If the string does match the
pattern, the operators return the integer index at which the match
begins. (Note that in Boolean expressions, nil
works like false
and any integer works like true
.)
The !~
operator is the
inverse of =~
: it calls =~
and returns true
if =~
returned nil
or false
if =~
returned an integer. You can redefine
!~
in Ruby 1.9 but not in Ruby
1.8.
The ===
operator is the
case-equality operator. It is used implicitly by case
statements (see Chapter 5). Its explicit use is much less common than
==
. Range
, Class
, and Regexp
define this operator as a kind of
membership or pattern-matching operator. Other classes inherit
Object
’s definition, which simply
invokes the ==
operator instead.
See Object Equality. Note that there is no !==
operator; if you want to negate ===
, you must do it yourself.
Ruby’s Boolean operators are built into the language and are not based
on methods: classes, for example, cannot define their own &&
method. The reason for this is
that Boolean operators can be applied to any value and must behave
consistently for any kind of operand. Ruby defines special true
and false
values but does not have a Boolean
type. For the purposes of all Boolean operators, the values false
and nil
are considered false. And every other
value, including true
, 0
, NaN
,
""
, []
, and {}
, is considered true. Note that !
is an exception; you can redefine this
operator in Ruby 1.9 (but not in Ruby 1.8). Note also that you can
define methods named and
, or
, and not
, but these methods are ordinary methods
and do not alter the behavior of the operators with the same
name.
Another reason that Ruby’s Boolean operators are a core part of the language rather than redefinable methods is that the binary operators are “short-circuiting.” If the value of the operation is completely determined by the lefthand operand, then the righthand operand is ignored and is never even evaluated. If the righthand operand is an expression with side effects (such as assignment, or an invocation of a method with side effects), then that side effect may or may not occur, based on the value of the lefthand operand.
&&
is a Boolean AND
operator. It returns a true value if both its left operand AND its
right operand are true values. Otherwise, it returns a false value.
Note that this description says “a true value” and “a false value”
instead of “the true
value” and
“the false
value.” &&
is often used in conjunction with
comparison operators, such as ==
and <
, in expressions like
this:
x == 0 && y > 1
The comparison and equality operators actually evaluate to the
values true
and false
, and in this case, the &&
operator is operating on actual
Boolean values. But this is not always the case. The operator can also
be used like this:
x && y
In this case, x
and y
can be anything. The value of the
expression is either the value of x
or it is the value of y
. If both
x
and y
are true values, then the value of the
expression is the value of y
. If
x
is a false value, then the value
of the expression is x
. Otherwise,
y
must be a false value, and the
value of the expression is y
.
Here’s how the &&
operator actually works. First, it evaluates its lefthand operand. If
this operand is nil
or false
, then it returns that value and skips
the righthand operand altogether. Otherwise, the lefthand operand is a
true value and the overall value of the &&
operator depends on the value of
the righthand operand. In this case, the operator evaluates its
righthand operand and returns that value.
The fact that &&
may
skip its righthand operand can be used to advantage in your code.
Consider this expression:
x && print(x.to_s)
This code prints the value of x
as a string, but only if x
is not nil
or false
.[*]
The ||
operator returns the
Boolean OR of its operands. It returns a true value if either of its
operands is a true value. If both operands are false values, then it
returns a false value. Like &&
, the ||
operator ignores its righthand operand if
its value has no impact on the value of the operation. The ||
operator works like this: first, it
evaluates its lefthand operand. If this is any value other than
nil
or false
, it simply returns that value.
Otherwise, it evaluates its righthand operand and returns that
value.
||
can be used as a
conjunction to join multiple comparison or equality
expressions:
x < 0 || y < 0 || z < 0 # Are any of the coordinates negative?
In this case, the operands to ||
will be actual true
or false
values, and the result will also be
true
or false
. But ||
is not restricted to working with
true
and false
. One idiomatic use of ||
is to return the first non-nil
value in a series of
alternatives:
# If the argument x is nil, then get its value from a hash of user preferences # or from a constant default value. x = x || preferences[:x] || Defaults::X
Note that &&
has
higher precedence than ||
. Consider
this expression:
1 || 2 && nil # => 1
The &&
is performed
first, and the value of this expression is 1. If the ||
was performed first, however, the value
would be nil
:
(1 || 2) && nil # => nil
The !
operator performs a
unary Boolean NOT. If the operand is nil
or false
, then the !
operator returns true
. Otherwise, !
returns false
.
The !
operator is at the
highest precedence. This means that if you want to compute the logical
inverse of an expression that itself uses operators, you must use
parentheses:
!(a && b)
Incidentally, one of the principles of Boolean logic allows the expression above to be rewritten as:
!a || !b
The and
, or
, and not
operators are low-precedence versions of
&&
, ||
, and !
. One reason to use these variants is
simply that their names are English and this can make your code easier
to read. Try reading this line of code, for example:
if x > 0 and y > 0 and not defined? d then d = Math.sqrt(x*x + y*y) end
Another reason for these alternate versions of the Boolean operators is the fact that they have lower precedence than the assignment operator. This means that you can write a Boolean expression such as the following that assigns values to variables until it encounters a false value:
if a = f(x) and b = f(y) and c = f(z) then d = g(a,b,c) end
This expression simply would not work if written with &&
instead of and
.
You should note that and
and
or
have the same precedence (and
not
is just slightly higher).
Because and
and or
have the same precedence, and &&
and ||
have different precedences, the following
two expressions compute different values:
x || y && nil # && is performed first => x x or y and nil # evaluated left-to-right => nil
We’ve seen ..
and ...
before in Ranges where they were
described as part of the Range
literal syntax. When the start and end points of a range are
themselves integer literals, as in 1..10
, the Ruby interpreter creates a
literal Range
object while parsing.
But if the start and end point expressions are anything more
complicated than integer literals, as in x..2*x
, then it is not really accurate to
call this a Range
literal. Instead,
it is a range creation expression. It follows, therefore, that
..
and ...
are operators rather than just range
literal syntax.
The ..
and ...
operators are not method-based and
cannot be redefined. They have relatively low precedence, which means
that they can usually be used without putting parentheses around the
left or right operands:
x+1..x*x
The value of these operators is a Range
object. x..y
is the same as:
Range.new(x,y)
And x...y
is the same
as:
Range.new(x,y,true)
When the ..
and ...
operators are used in a conditional, such as an if
statement, or in a loop, such as a
while
loop (see Chapter 5 for more about conditionals and loops), they do
not create Range
objects.
Instead, they create a special kind of Boolean expression called a
flip-flop. A flip-flop expression evaluates to
true
or false
, just as comparison and equality
expressions do. The extraordinarily unusual thing about a flip-flop
expression, however, is that
its value depends on the value of previous evaluations. This means
that a flip-flop expression has state associated with it; it must
remember information about previous evaluations. Because it has
state, you would expect a flip-flop to be an object of some sort.
But it isn’t—it’s a Ruby expression, and the Ruby interpreter stores
the state (just a single Boolean value) it requires in its internal
parsed representation of the expression.
With that background in mind, consider the flip-flop in the
following code. Note that the first ..
in the code creates a Range
object. The second one creates the
flip-flop expression:
(1..10).each {|x| print x if x==3..x==5 }
The flip-flop consists of two Boolean expressions joined with
the ..
operator, in the context
of a conditional or loop. A flip-flop expression is false
unless and until the lefthand
expression evaluates to true
.
Once that expression has become true
, the expression “flips” into a
persistent true
state. It remains
in that state, and subsequent evaluations return true
until the righthand expression
evaluates to true
. When that
happens, the flip-flop “flops” back to a persistent false
state. Subsequent evaluations of the
expression return false
until the
lefthand expression becomes true
again.
In the code example, the flip-flop is evaluated repeatedly,
for values of x
from 1
to 10.
It starts off in the false
state, and evaluates to false
when x
is 1
and 2
. When x==3
, the flip-flop
flips to true
and returns
true
. It continues to return
true
when x
is 4
and 5
. When x==5
, however, the flip-flop flops back to
false
, and returns false
for the remaining values of x
. The result is that this code prints
345
.
Flip-flops can be written with either ..
or ...
. The difference is that when a
..
flip-flop flips to true
, it returns true
but also tests its righthand
expression to see if it should flop its internal state back to
false
. The ...
form waits for its next evaluation
before testing the righthand expression. Consider these two
lines:
# Prints "3". Flips and flops back when x==3 (1..10).each {|x| print x if x==3..x>=3 } # Prints "34". Flips when x == 3 and flops when x==4 (1..10).each {|x| print x if x==3...x>=3 } # Prints "34"
Flip-flops are a fairly obscure feature of Ruby and are probably best avoided in your code. They are not unique to Ruby, however. Ruby inherits this feature from Perl, which in turn inherits them from the Unix text-processing tools sed and awk.[*] Flip-flops were originally intended for matching the lines of a text file between a start pattern and an end pattern. This continues to be a useful way to use them. The following simple Ruby program demonstrates a flip-flop. It reads a text file line-by-line and prints any line that contains the text “TODO”. It then continues printing lines until it reads a blank line:
ARGF.each do |line| # For each line of standard in or of named files print line if line=~/TODO/..line=~/^$/ # Print lines when flip-flop is true end
It is difficult to formally describe the precise behavior of a
flip-flop. It is easier to understand flip-flops by studying code
that behaves in an equivalent way. The following function behaves
like the flip-flop x==3..x==5
. It
hardcodes the lefthand and righthand conditions into the function
itself, and it uses a global variable to store the state of the
flip-flop:
$state = false # Global storage for flip-flop state def flipflop(x) # Test value of x against flip-flop if !$state # If saved state is false result = (x == 3) # Result is value of lefthand operand if result # If that result is true $state = !(x == 5) # Then saved state is not of the righthand operand end result # Return result else # Otherwise, if saved state is true $state = !(x == 5) # Then save the inverse of the righthand operand true # And return true without testing lefthand end end
With this flip-flop function defined, we can write the
following code, which prints 345
just like our earlier example:
(1..10).each {|x| print x if flipflop(x) }
The following function simulates the behavior of the three-dot
flip-flop x==3...x>=3
:
$state2 = false def flipflop2(x) if !$state2 $state2 = (x == 3) else $state2 = !(x >= 3) true end end # Now try it out (1..10).each {|x| print x if x==3...x>=3 } # Prints "34" (1..10).each {|x| print x if flipflop2(x) } # Prints "34"
The ?:
operator is known
as the conditional operator. It is the only ternary operator (three
operands) in Ruby. The first operand appears before the question mark.
The second operand appears between the question mark and the colon.
And the third operand appears
after the colon.
The ?:
operator always
evaluates its first operand. If the first operand is anything other
than false
or nil
, the value of the expression is the
value of the second operand. Otherwise, if the first operand is
false
or nil
, then the value of the expression is the
value of the third operand. In either case, one of the operands is
never evaluated (which matters if it includes side effects like
assignment). Here is an example use of this operator:
"You have #{n} #{n==1 ? 'message' : 'messages'}"
As you can see, the ?:
operator acts like a compact if/then/else
statement. (Ruby’s if
conditional
is described in Chapter 5.) The first operand is the
condition that is being tested, like the expression after the if
. The second operand is like the code that
follows the then
. And the third
operand is like the code that follows the else
. The difference between the ?:
operator and the if
statement, of course, is that the
if
statement allows arbitrary
amounts of code in its then
and
else
clauses, whereas the ?:
operator allows only single
expressions.
The ?:
operator has fairly
low precedence, which means that it is usually not necessary to put
parentheses around the operands. If the first operand uses the
defined?
operator, or if the second
and third operands perform assignments, then parentheses are
necessary. Remember that Ruby allows method names to end with a
question mark. If the first operand of the ?:
operator ends with an identifier, you
must put parentheses around the first operand or include a
disambiguating space between that operand and the question mark. If
you don’t do this, the Ruby interpreter thinks that the question mark
of the operator is part of the previous identifier. For
example:
x==3?y:z # This is legal 3==x?y:z # Syntax error: x? is interpreted as a method name (3==x)?y:z # Okay: parentheses fix the problem 3==x ?y:z # Spaces also resolve the problem
The question mark must appear on the same line as the first argument. In Ruby 1.8, the colon must appear on the same line as the second argument. In Ruby 1.9, however, a newline is allowed before the colon. You must follow the colon by a space in this case, however, so it doesn’t appear to introduce a symbol literal.
Table 4-2 (earlier in this chapter) says
that the ?:
operator is
right-associative. If the operator is used twice in the same
expression, the rightmost one is grouped:
a ? b : c ? d : e # This expression... a ? b : (c ? d : e) # is evaluated like this.. (a ? b : c) ? d : e # NOT like this
This kind of ambiguity is actually fairly rare with the ?:
operator. The following expression uses
three conditional operators to compute the maximum value of three
variables. No parentheses are required (although spaces are required
before the question marks), as
there is only one possible way to parse the statement:
max = x>y ? x>z ? x : z : y>z ? y : z max = x>y ? (x>z ? x : z) : (y>z ? y : z) # With explicit parentheses
You’ve already read about assignment expressions in Assignments. It is worth noting here a few points about the assignment operators used in those expressions. First, the value of an assignment expression is the value (or an array of the values) that appears on the righthand side of the assignment operator. Second, assignment operators are right-associative. Points one and two together are what make expressions like this one work:
x = y = z = 0 # Assign zero to variables x, y, and z x = (y = (z = 0)) # This equivalent expression shows order of evaluation
Third, note that assignment has very low precedence.
Precedence rules mean that just about anything that follows an
assignment operator will be evaluated before the assignment is performed. The main
exceptions are the and
, or
, and not
operators.
Finally, note that although assignment operators cannot be
defined as methods, the compound assignment operators like +=
use redefinable operators like +
. Redefining the +
operator does not affect the assignment
performed by the +=
operator, but
it does affect the addition performed by that operator.
defined?
is a unary operator that tests whether its operand is
defined or not. Normally, using an undefined variable or method raises
an exception. When the expression on the right of the defined?
operator uses an undefined variable
or method (including operators defined as methods), defined?
simply returns nil
. Similarly, defined?
returns nil
if the operand is an expression that
uses yield
or super
in an inappropriate context (i.e.,
when there is no block to yield to, or no superclass method to
invoke). It is important to
understand that the expression that is the operand to defined?
is not actually evaluated; it is
simply checked to see whether it could be
evaluated without error. Here is a typical use of the defined?
operator:
# Compute f(x), but only if f and x are both defined y = f(x) if defined? f(x)
If the operand is defined, the defined?
operator returns a string. The
content of this returned string is usually unimportant; what matters
is that it is a true value—neither nil
nor false
. It is possible, however, to inspect
the value returned by this operator to learn something about the type
of the expression on the righthand side. Table 4-3 lists the possible return values of this
operator.
Table 4-3. Return values of the defined? operator
Operand expression type | Return value |
---|---|
Reference to defined local variable | "local-variable" |
Reference to defined block local variable (Ruby 1.8 only) | "local-variable(in-block)" |
Reference to defined global variable | "global-variable" |
Special regular expression global variables, $& , $+ , $` , $' , and $1 to $9 , when defined following a
successful match (Ruby 1.8 only) | Name of variable, as a string |
Reference to defined constant | "constant" |
Reference to defined instance variable | "instance-variable" |
Reference to defined class variable | "class variable"
(note no hyphen) |
nil | "nil" (note this is
a string) |
true , false | "true" , "false" |
self | "self" |
yield when there is
a block to yield to (see also Kernel method block_given? ) | "yield" |
super when in
context where it is allowed | "super" |
Assignment (assignment is not actually performed) | "assignment" |
Method invocation, including operators defined as
methods (method is not actually invoked and need not have
correct number of arguments; see also Object.respond_to? ) | "method" |
Any other valid expression, including literals and built-in operators | "expression" |
Any expression that uses an undefined variable or
method name, or that uses yield or super where they are not
allowed | nil |
The defined?
operator has
very low precedence. If you want to test whether two variables are defined, use and
instead of &&
:
defined? a and defined? b # This works defined? a && defined? b # Evaluated as: defined?((a && defined? b))
rescue
, if
, unless
, while
, and until
are conditional, looping, and exception-handling statements
that affect the flow-of-control of a Ruby program. They can also be
used as statement modifiers, in code like this:
print x if x
In this modifier form, they can be considered operators in which
the value of the righthand expression affects the execution of the
lefthand expression. (Or, in the case of the rescue
modifier, the exception status of the
lefthand expression affects the execution of the righthand
operand.)
It is not particularly useful to describe these keywords as
operators. They are documented, in both their statement and expression
modifier form, in Chapter 5. The keywords are listed
in Table 4-2 simply to show their precedence
relative to other operators.
Note that they all have very low precedence, but that the rescue
statement modifier has higher
precedence than assignment.
Most of Ruby’s operators are written using punctuation characters. Ruby’s grammar also uses a number of punctuation characters that are not operators. Although we’ve seen (or will see) much of this nonoperator punctuation elsewhere in this book, let’s review it here:
()
Parentheses are an optional part of method definition and invocation syntax. It is better to think of method invocation as a special kind of expression than to think of
()
as a method-invocation operator. Parentheses are also used for grouping to affect the order of evaluation of subexpressions.[]
Square brackets are used in array literals and for querying and setting array and hash values. In that context, they are syntactic sugar for method invocation and behave somewhat like redefinable operators with arbitrary arity. See Method Invocations and Assigning to Attributes and Array Elements.
{}
Curly braces are an alternative to
do/end
in blocks, and are also used in hash literals. In neither case do they act as operators..
and::
.
and::
are used in qualified names, separating the name of a method from the object on which it is invoked, or the name of a constant from the module in which it is defined. These are not operators because the righthand side is not a value but an identifier.;
,,
, and=>
These punctuation characters are separators rather than operators. The semicolon (
;
) is used to separate statements on the same line; the comma (,
) is used to separate method arguments and the elements of array and hash literals; and the arrow (=>
) is used to separate hash keys from hash values in hash literals.:
A colon is used to prefix symbol literals and is also used in Ruby 1.9 hash syntax.
*
,&
, and<
These punctuation characters are operators in some contexts, but they are also used in ways that are not operators. Putting
*
before an array in an assignment or method invocation expression expands or unpacks the array into its individual elements. Although it is sometimes known as the splat operator, it is not really an operator;*a
cannot stand alone as an expression.&
can be used in a method declaration before the name of the last method argument, and this causes any block passed to the method to be assigned to that argument. (See Chapter 6.) It can also be used in method invocation to pass a proc to a method as if it were a block.<
is used in class definitions to specify the superclass of class.
[*] Some implementations of this operator may return any value
less than 0
or any value
greater than 0
, instead of
–1
and +1
. If you implement <=>
, your implementation should
return –1
, 0
, or +1
. But if you use <=>
, you should test for values
less than or greater than zero, rather than assuming that the
result will always be –1
,
0
, or +1
.
[*] Just because an expression can be written this way doesn’t mean that it should be. In Chapter 5, we’ll see that this expression is better written as:
print(x.to_s) if x
[*] ..
creates an
awk-style flip-flop, and ...
creates a
sed-style flip-flop.
Get The Ruby Programming Language now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.