So far we've been fairly cavalier in our use of expressions in Ruby.
After all, a=b+c is pretty standard
stuff. You could write a whole heap of Ruby code without reading any
of this chapter.
But it wouldn't be as much fun ;-).
One of the first differences with Ruby is that anything that can
reasonably return a value does: just about everything is an
expression. What does this mean in practice?
Some obvious things include the ability to chain statements together.
a = b = c = 0
»
0
[ 3, 1, 7, 0 ].sort.reverse
»
[7, 3, 1, 0]
Perhaps less obvious, things that are normally statements in C
or Java are expressions in Ruby. For example, the if and
case statements both return the value of the last expression
executed.
songType = if song.mp3Type == MP3::Jazz
if song.written < Date.new(1935, 1, 1)
Song::TradJazz
else
Song::Jazz
end
else
Song::Other
end
rating = case votesCast
when 0...10 then Rating::SkipThisOne
when 10...50 then Rating::CouldDoBetter
else Rating::Rave
end
We'll talk more about if and case starting
on page 79.
Ruby has the basic set of operators (+, -, *, /, and so on) as well
as a few surprises. A complete list of the operators, and their
precedences, is given in Table 18.4 on page 219.
In Ruby, many operators are actually method calls. When you write
a*b+c
you're actually asking the object referenced by a to execute the
method ``*'', passing in the parameter b. You then ask the
object that results from that calculation to execute ``+'',
passing c as a parameter. This is exactly equivalent
to writing
(a.*(b)).+(c)
Because everything is an object, and because you can redefine
instance methods, you can always redefine basic arithmetic if you
don't like the answers you're getting.
class Fixnum
alias oldPlus +
def +(other)
oldPlus(other).succ
end
end
1 + 2
»
4
a = 3
a += 4
»
8
More useful is the fact that classes that you write can participate in
operator expressions just as if they were built-in objects. For
example, we might want to be able to extract a number of seconds of
music from the middle of a song. We could using the indexing operator
``[]'' to specify the music to be extracted.
class Song
def [](fromTime, toTime)
result = Song.new(self.title + " [extract]",
self.artist,
toTime - fromTime)
result.setStartTime(fromTime)
result
end
end
This code fragment extends class Song with the method
``[]'', which takes two parameters (a start time and an end
time). It returns a new song, with the music clipped to the given
interval. We could then play the introduction to a song with code
such as:
As well as the obvious operator expressions and method calls, and the
(perhaps) less obvious statement expressions (such as if and
case), Ruby has a few more things that you can use in
expressions.
If you enclose a string in backquotes, or use the delimited form
prefixed by %x, it will (by default) be executed as a command by
your underlying operating system.
The value of the expression is the
standard output of that command. Newlines will not be stripped, so it is
likely that the value you get back will have a trailing return or
linefeed character.
`date`
»
"Sun Jun 9 00:08:26 CDT 2002\n"
`dir`.split[34]
»
"lib_singleton.tip"
%x{echo "Hello there"}
»
"Hello there\n"
You can use expression expansion and all the usual escape sequences in
the command string.
for i in 0..3
status = `dbmanager status id=#{i}`
# ...
end
The exit status of the command is available in the global variable
$?.
In the description of the command output expression, we said that the
string in backquotes would ``by default'' be executed as a command. In
fact, the string is passed to the method called Kernel::`
(a single backquote). If you want, you can override
this.
alias oldBackquote `
def `(cmd)
result = oldBackquote(cmd)
if $? != 0
raise "Command #{cmd} failed"
end
result
end
print `date`
print `data`
produces:
Sun Jun 9 00:08:26 CDT 2002
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
from prog.rb:10
Just about every example we've given so far in this book has featured
assignment. Perhaps it's about time we said something about it.
An assignment statement sets the variable or attribute on its left
side (the lvalue) to refer to the value on the right (the
rvalue).
It then returns that value as the result of the
assignment expression. This means that you can chain assignments and
that you can
perform assignments in some unexpected places.
a = b = 1 + 2 + 3
a
»
6
b
»
6
a = (b = 1 + 2) + 3
a
»
6
b
»
3
File.open(name = gets.chomp)
There are two basic forms of assignment in Ruby. The first assigns an
object reference to a variable or constant. This form of assignment
is hard-wired into the language.
instrument = "piano"
MIDDLE_A = 440
The second form of assignment involves having an object attribute or
element reference on the left-hand side.
aSong.duration = 234
instrument["ano"] = "ccolo"
These forms are special, because they are implemented by calling
methods in the lvalues, which means you can override them.
We've already seen how to define a writable object attribute. Simply
define a method name ending in an equals sign. This method receives as
its parameter the assignment's rvalue.
class Song
def duration=(newDuration)
@duration = newDuration
end
end
There is no reason that these attribute setting methods must
correspond with internal instance variables, or that there has to be
an attribute reader for every attribute writer (or vice versa).
class Amplifier
def volume=(newVolume)
self.leftChannel = self.rightChannel = newVolume
end
# ...
end
Sidebar: Using Accessors Within a Class
Why did we write self.leftChannel in the example on page
74? Well, there's a hidden gotcha with writable
attributes. Normally, methods within a class can invoke other
methods in the same class and its superclasses in functional form
(that is, with an implicit receiver of self). However, this
doesn't work with attribute writers. Ruby sees the assignment and
decides that the name on the left must be a local variable, not a
method call to an attribute writer.
class BrokenAmplifier
attr_accessor :leftChannel, :rightChannel
def volume=(vol)
leftChannel = self.rightChannel = vol
end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel
»
99
ba.rightChannel
»
5
We forgot to put ``self.'' in front of the assignment to
leftChannel, so Ruby stored the new value in a local variable of
method volume=; the object's attribute never got updated.
This can be a tricky bug to track down.
During your first week in a programming course (or the second semester
if it was a party school), you may have had to write code to swap the
values in two variables:
int a = 1;
int b = 2;
int temp;
temp = a;
a = b;
b = temp;
You can do this much more cleanly in Ruby:
a, b = b, a
Ruby assignments are effectively performed in parallel, so the values
assigned are not affected by the assignment itself. The values on the
right-hand side are evaluated in the order in which they appear before any
assignment is made to variables or attributes on the left. A somewhat
contrived example illustrates this. The second line assigns to the
variables a, b, and c the values of the expressions
x, x+=1, and x+=1, respectively.
x = 0
»
0
a, b, c = x, (x += 1), (x += 1)
»
[0, 1, 2]
When an assignment has more than one lvalue, the assignment expression
returns an array of the rvalues.
If an assignment contains more lvalues than rvalues, the excess
lvalues are
set to nil. If a multiple assignment contains more rvalues than
lvalues, the extra rvalues are ignored. As of Ruby 1.6.2, if an
assignment has one lvalue and multiple rvalues, the rvalues are
converted to an array and assigned to the lvalue.
You can collapse and expand arrays using Ruby's parallel assignment
operator. If the last lvalue is preceded by an asterisk, all the
remaining rvalues will be collected and assigned to that lvalue as an
array. Similarly, if the last rvalue is an array, you can prefix it
with an asterisk, which effectively expands it into its constituent
values in place. (This is not necessary if the rvalue is the only
thing on the right-hand side---the array will be expanded
automatically.)
Parallel assignments have one more feature worth mentioning.
The left-hand side of an assignment may contain a parenthesized list of
terms. Ruby treats these terms as if they were a nested assignment
statement. It extracts out the corresponding rvalue, assigning it to
the parenthesized terms, before continuing with the higher-level
assignment.
In common with many other languages, Ruby has a syntactic shortcut:
a=a+2 may be written as a+=2.
The second form is converted internally to the first. This means that
operators that you have defined as methods in your own classes work as
you'd expect.
Ruby has several different mechanisms for conditional execution of
code; most of them should feel familiar, and many have some neat
twists. Before we get into them, though, we need to spend a short time
looking at boolean expressions.
Ruby has a simple definition of truth. Any value that is not nil or
the constant false is true. You'll find that the library
routines use this fact consistently. For example, IO#gets,
which returns the next line from a file, returns nil at end of
file, enabling you to write loops such as:
while line = gets
# process line
end
However, there's a trap here for C, C++, and Perl
programmers. The number zero is not interpreted as a false
value. Neither is a zero-length string. This can be a tough habit to
break.
Ruby supports all the standard boolean operators and introduces the
new operator defined?.
Both ``and'' and ``&&''
evaluate to true only if both operands are
true. They evaluate the second operand only if the first is true
(this is sometimes known as ``short-circuit evaluation''). The only
difference in the two forms is precedence (``and'' binds lower than
``&&'').
Similarly, both ``or'' and ``||''
evaluate to true if either operand
is true. They evaluate their second operand only if the first is
false. As with ``and'', the only difference between ``or'' and
``||'' is their precedence.
Just to make life interesting, ``and'' and ``or'' have the
same precedence, while ``&&'' has a higher precedence than
``||''.
``not'' and ``!''
return the opposite of their operand (false if the
operand is true, and true if the operand is false). And, yes, ``not''
and ``!'' differ only in precedence.
All these precedence rules are summarized in Table
18.4 on page 219.
The defined?
operator returns nil if its argument (which can be
an arbitrary expression) is not defined, otherwise it returns a
description of that argument. If the argument is yield,
defined? returns the string ``yield'' if a code block is
associated with the current context.
defined? 1
»
"expression"
defined? dummy
»
nil
defined? printf
»
"method"
defined? String
»
"constant"
defined? $&
»
nil
defined? $_
»
"global-variable"
defined? Math::PI
»
"constant"
defined? ( c,d = 1,2 )
»
"assignment"
defined? 42.abs
»
"method"
In addition to the boolean operators, Ruby objects support comparison
using the methods ==, ===, <=>, =~, eql?,
and equal? (see Table 7.1 on page 79). All but <=>
are defined in class Object but are often overridden by
descendents to provide appropriate semantics. For example, class
Array redefines == so that two array objects are equal if
they have the same number of elements and corresponding elements are
equal.
Common comparison operators
Operator
Meaning
==
Test for equal value.
===
Used to test equality within a when clause of a
case statement.
<=>
General comparison operator. Returns -1, 0, or +1,
depending on whether its receiver is less than, equal to, or
greater than its argument.
<, <=, >=, >
Comparison operators for less than, less than or
equal, greater than or equal, and greater than.
=~
Regular expression pattern match.
eql?
True if the receiver and argument have both the same
type and equal values. 1 == 1.0 returns true,
but 1.eql?(1.0) is false.
equal?
True if the receiver and argument have the same
object id.
Both == and =~ have negated forms, != and !~.
However, these are converted by Ruby when your program is read.
a!=b is equivalent to !(a==b),
and a!~b is the
same as !(a=~b). This means that if you write a class that
overrides == or =~ you get a working != and !~
for free. But on the flip side, this also means that you cannot define
!= and !~ independent of == and =~, respectively.
You can use a Ruby range as a boolean expression.
A
range such as exp1..exp2 will evaluate as false
until exp1 becomes true. The range will then evaluate as true
until exp2 becomes true. Once this happens, the range resets,
ready to fire again. We show some examples of this
on page 82.
Finally, you can use a bare regular expression as a boolean
expression. Ruby expands it to $_=~/re/.
An if expression in Ruby is pretty similar to ``if'' statements
in other languages.
if aSong.artist == "Gillespie" then
handle = "Dizzy"
elsif aSong.artist == "Parker" then
handle = "Bird"
else
handle = "unknown"
end
If you lay out your if statements on multiple lines, you can
leave off the then keyword.
if aSong.artist == "Gillespie"
handle = "Dizzy"
elsif aSong.artist == "Parker"
handle = "Bird"
else
handle = "unknown"
end
However, if you lay your code out more tightly, the then keyword
is necessary to separate the boolean expression from the following
statements.
if aSong.artist == "Gillespie" then handle = "Dizzy"
elsif aSong.artist == "Parker" then handle = "Bird"
else handle = "unknown"
end
You can have zero or more elsif clauses and an optional
else clause.
As we've said before, if is
an expression, not a statement---it returns a value. You don't have
to use the value of an if expression, but it can come in handy.
handle = if aSong.artist == "Gillespie" then
"Dizzy"
elsif aSong.artist == "Parker" then
"Bird"
else
"unknown"
end
Ruby also has a negated form of the if statement:
unless aSong.duration > 180 then
cost = .25
else
cost = .35
end
Finally, for the C fans out there, Ruby also supports the C-style
conditional expression:
cost = aSong.duration > 180 ? .35 : .25
The conditional expression returns the value of either the expression
before or the expression after the colon, depending on whether the
boolean expression before the question mark evaluates to true
or false. In this case, if the song duration is greater than 3
minutes, the expression returns .35. For shorter songs, it returns
.25. Whatever the result, it is then assigned to cost.
The Ruby case expression is a powerful beast: a multiway if
on steroids.
case inputLine
when "debug"
dumpDebugInfo
dumpSymbols
when /p\s+(\w+)/
dumpVariable($1)
when "quit", "exit"
exit
else
print "Illegal command: #{inputLine}"
end
As with if, case returns the value of the last expression
executed, and you also need a then keyword if the
expression is on the same line as the condition.
kind = case year
when 1850..1889 then "Blues"
when 1890..1909 then "Ragtime"
when 1910..1929 then "New Orleans Jazz"
when 1930..1939 then "Swing"
when 1940..1950 then "Bebop"
else "Jazz"
end
case operates by comparing the target (the expression after the
keyword case) with each of the comparison expressions after the
when keywords. This test is done using
comparison===target.
As long as a class defines
meaningful semantics for === (and all the built-in classes do),
objects of that class can be used in case expressions.
For example, regular expressions define === as a simple pattern match.
case line
when /title=(.*)/
puts "Title is #$1"
when /track=(.*)/
puts "Track is #$1"
when /artist=(.*)/
puts "Artist is #$1"
end
Ruby classes are instances of class Class, which defines ===
as a test to see if the argument is an instance of the class or one of
its superclasses. So (abandoning the benefits of polymorphism and
bringing the gods of refactoring down around your ears), you can test
the class of objects:
case shape
when Square, Rectangle
# ...
when Circle
# ...
when Triangle
# ...
else
# ...
end
Don't tell anyone, but Ruby has pretty primitive built-in looping
constructs.
The while loop executes its body zero or more times as long as
its condition is true. For example, this common idiom reads until
the input is exhausted.
while gets
# ...
end
There's also a negated form that executes the body until the
condition becomes true.
until playList.duration > 60
playList.add(songList.pop)
end
As with if and unless, both of the loops can also be used
as statement modifiers.
a *= 2 while a < 100
a -= 10 until a < 100
On page 78 in the section on boolean
expressions,
we said that a range can be used as a kind of flip-flop, returning true
when some event happens and then staying true until a second event occurs.
This facility is normally used within loops. In the example that
follows, we read a text file containing the first ten ordinal numbers
(``first,'' ``second,'' and so on)
but only print the lines starting with the one that matches
``third'' and ending with the one that matches ``fifth.''
file = File.open("ordinal")
while file.gets
print if /third/ .. /fifth/
end
produces:
third
fourth
fifth
The elements of a range used in a boolean expression can themselves be
expressions. These are evaluated each time the overall boolean
expression is evaluated. For example, the following code uses the fact
that the variable $. contains the current input line number to
display line numbers one through three and those between a match of
/eig/ and /nin/.
file = File.open("ordinal")
while file.gets
print if ($. == 1) || /eig/ .. ($. == 3) || /nin/
end
produces:
first
second
third
eighth
ninth
There's one wrinkle when while and until are used as statement
modifiers. If the statement they are modifying is a
begin/end block,
the code in the block will always execute
at least one time, regardless of the value of the boolean expression.
print "Hello\n" while false
begin
print "Goodbye\n"
end while false
If you read the beginning of the previous section, you might have been
discouraged. ``Ruby has pretty primitive built-in looping
constructs,'' it said. Don't despair, gentle reader, for there's good
news. Ruby doesn't need any sophisticated built-in loops, because all
the fun stuff is implemented using Ruby iterators.
For example, Ruby doesn't have a ``for'' loop---at least not the kind
you'd find in C, C++, and Java. Instead, Ruby uses methods defined in
various built-in classes to provide equivalent, but less error-prone,
functionality.
Let's look at some examples.
3.times do
print "Ho! "
end
produces:
Ho! Ho! Ho!
It's easy to avoid fencepost and off-by-1 errors; this loop will
execute three times, period. In addition to times, integers
can loop over specific ranges by calling downto,
upto, and step. For instance, a traditional ``for''
loop that runs from 0 to 9 (something like i=0; i < 10; i++)
is written as follows.
0.upto(9) do |x|
print x, " "
end
produces:
0 1 2 3 4 5 6 7 8 9
A loop from 0 to 12 by 3 can be written as follows.
0.step(12, 3) {|x| print x, " " }
produces:
0 3 6 9 12
Similarly, iterating over arrays and other containers is made easy
using their each method.
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
produces:
1 1 2 3 5
And once a class supports each, the additional methods in the
Enumerable module (documented beginning on page 403
and summarized on pages 102--103)
become available. For example, the File class provides an
each method, which returns each line of a file in turn. Using
the grep method in Enumerable, we could iterate over only
those lines that meet a certain condition.
File.open("ordinal").grep /d$/ do |line|
print line
end
produces:
second
third
Last, and probably least, is the most basic loop of all. Ruby provides
a built-in iterator called loop.
loop {
# block ...
}
The loop iterator calls the associated block forever (or at
least until you break out of the loop, but you'll have to read ahead
to find out how to do that).
Earlier we said that the only built-in Ruby looping primitives were
while and until. What's this ``for'' thing, then?
Well, for is almost a lump of syntactic sugar. When you write
for aSong in songList
aSong.play
end
Ruby translates it into something like:
songList.each do |aSong|
aSong.play
end
The only difference between the for loop and the each
form is the scope of local variables that are defined in the body.
This is discussed on page 87.
You can use for
to iterate over any object that responds to the method each, such
as an Array or a Range.
for i in ['fee', 'fi', 'fo', 'fum']
print i, " "
end
for i in 1..3
print i, " "
end
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
print i.chomp, " "
end
produces:
fee fi fo fum 1 2 3 second third
As long as your class defines a sensible each method, you can use
a for loop to traverse it.
class Periods
def each
yield "Classical"
yield "Jazz"
yield "Rock"
end
end
periods = Periods.new
for genre in periods
print genre, " "
end
The loop control constructs break, redo, and next
let you alter the normal flow through a loop or iterator.
break terminates the immediately enclosing loop; control resumes
at the statement following the block. redo repeats the loop from
the start, but without reevaluating the condition or fetching the
next element (in an iterator). next skips to the end of the
loop, effectively starting the next iteration.
while gets
next if /^\s*#/ # skip comments
break if /^END/ # stop at end
# substitute stuff in backticks and try again
redo if gsub!(/`(.*?)`/) { eval($1) }
# process line ...
end
These keywords can also be used with any of the iterator-based
looping mechanisms:
i=0
loop do
i += 1
next if i < 3
print i
break if i > 4
end
The redo statement causes a loop to repeat the current
iteration. Sometimes, though, you need to wind the loop right back to
the very beginning. The retry statement is just the
ticket. retry restarts any kind of iterator loop.
for i in 1..100
print "Now at #{i}. Restart? "
retry if gets =~ /^y/i
end
Running this interactively, you might see
Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
. . .
retry will reevaluate any arguments to the iterator before
restarting it. The online Ruby documentation has the following example
of a do-it-yourself until loop.
def doUntil(cond)
yield
retry unless cond
end
i = 0
doUntil(i > 3) {
print i, " "
i += 1
}
The while, until, and for loops are built into the
language and do not introduce new scope; previously existing locals
can be used in the loop, and any new locals created will be available
afterward.
The blocks used by iterators (such as loop and each) are
a little different. Normally, the local variables created in these
blocks are not accessible outside the block.
[ 1, 2, 3 ].each do |x|
y = x + 1
end
[ x, y ]
produces:
prog.rb:4: undefined local variable or method `x' for #<Object:0x401c2ce0> (NameError)
However, if at the time the block executes a local variable
already exists with the same name as that of a
variable in the block, the existing local variable will be used in the
block. Its value will therefore be available after the block finishes.
As the following example shows, this applies both to normal variables in the
block and to the block's parameters.