Operators

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)NAMOperation
! ~ +1RYBoolean NOT, bitwise complement, unary plus[a]
**2RYExponentiation
-1RYUnary minus (define with -@)
* / %2LYMultiplication, division, modulo (remainder)
+ -2LYAddition (or concatenation), subtraction
<< >>2LY Bitwise shift-left (or append), bitwise shift-right
&2LYBitwise AND
| ^2LYBitwise OR, bitwise XOR
< <= >= >2LYOrdering
== === != =~ !~ <=>2NYEquality, pattern matching, comparison[b]
&&2LNBoolean AND
||2LNBoolean OR
.. ...2NNRange creation and Boolean flip-flops
?:3RNConditional
rescue2LNException-handling modifier
=
**= *= /= %= += -=
<<= >>=
&&= &= ||= |= ^=
2RNAssignment
defined?1NNTest variable definition and type
not1RNBoolean NOT (low precedence)
and or2LNBoolean AND, Boolean OR (low precedence)
if unless while until2NNConditional and loop modifiers

[a] ! may not be redefined prior to Ruby 1.9. Define unary plus with +@.

[b] != and !~ may not be redefined prior to Ruby 1.9.

Unary + and –

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.

Exponentiation: **

** 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.)

Arithmetic: +, –, *, /, and %

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.

Shift and Append: << and >>

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

Complement, Union, Intersection: ~, &, |, and ^

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 1s-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.

Comparison: <, <=, >, >=, and <=>

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.

Equality: ==, !=, =~, !~, and ===

== 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.

Boolean Operators: &&, ||, !, and, or, not

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 

Ranges and Flip-Flops: .. and ...

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)

Boolean flip-flops

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" 

Conditional: ?:

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

Assignment Operators

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.

The defined? 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 typeReturn 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 allowednil

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

Statement Modifiers

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.

Nonoperators

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.