|
|||
Previous < |
Contents ^
|
Next >
|
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]
|
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 |
if
and case
starting
on page 79.
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) |
class Fixnum
|
||
alias oldPlus +
|
||
def +(other)
|
||
oldPlus(other).succ
|
||
end
|
||
end
|
||
|
||
1 + 2
|
» |
4
|
a = 3
|
||
a += 4
|
» |
8
|
[]
'' 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 |
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:
aSong[0, 0.15].play |
if
and
case
), Ruby has a few more things that you can use in
expressions.
%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"
|
for i in 0..3 status = `dbmanager status id=#{i}` # ... end |
$?
.
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` |
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 |
a = b = 1 + 2 + 3
|
||
a
|
» |
6
|
b
|
» |
6
|
a = (b = 1 + 2) + 3
|
||
a
|
» |
6
|
b
|
» |
3
|
File.open(name = gets.chomp)
|
instrument = "piano" MIDDLE_A = 440 |
aSong.duration = 234 instrument["ano"] = "ccolo" |
class Song def duration=(newDuration) @duration = newDuration end end |
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.
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.
|
int a = 1; int b = 2; int temp; temp = a; a = b; b = temp; |
a, b = b, a |
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]
|
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.)
a = [1, 2, 3, 4]
|
|||
b, c = a | » | b == 1, | c == 2 |
b, *c = a | » | b == 1, | c == [2, 3, 4] |
b, c = 99, a | » | b == 99, | c == [1, 2, 3, 4] |
b, *c = 99, a | » | b == 99, | c == [[1, 2, 3, 4]] |
b, c = 99, *a | » | b == 99, | c == 1 |
b, *c = 99, *a | » | b == 99, | c == [1, 2, 3, 4] |
b, (c, d), e = 1,2,3,4 | » | b == 1, | c == 2, | d == nil, | e == 3 |
b, (c, d), e = [1,2,3,4] | » | b == 1, | c == 2, | d == nil, | e == 3 |
b, (c, d), e = 1,[2,3],4 | » | b == 1, | c == 2, | d == 3, | e == 4 |
b, (c, d), e = 1,[2,3,4],5 | » | b == 1, | c == 2, | d == 3, | e == 5 |
b, (c,*d), e = 1,[2,3,4],5 | » | b == 1, | c == 2, | d == [3, 4], | e == 5 |
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.
class Bowdlerize
|
||
def initialize(aString)
|
||
@value = aString.gsub(/[aeiou]/, '*')
|
||
end
|
||
def +(other)
|
||
Bowdlerize.new(self.to_s + other.to_s)
|
||
end
|
||
def to_s
|
||
@value
|
||
end
|
||
end
|
||
|
||
a = Bowdlerize.new("damn ")
|
» |
d*mn
|
a += "shame"
|
» |
d*mn sh*m*
|
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 |
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"
|
==
, ===
, <=>
, =~
, 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
|
==
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/
.
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
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 |
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 |
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 |
if
statement:
unless aSong.duration > 180 then cost = .25 else cost = .35 end |
cost = aSong.duration > 180 ? .35 : .25 |
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
.
mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/ puts "a = #{a}" if fDebug print total unless total == 0 |
if
modifier, the preceding expression will be evaluated only
if the condition is true. unless
works the other way around.
while gets next if /^#/ # Skip comments parseLine unless /^$/ # Don't parse empty lines end |
if
itself is an expression, you can get really obscure
with statements such as:
if artist == "John Coltrane" artist = "'Trane" end unless nicknames == "no" |
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 |
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 |
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 |
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 |
until playList.duration > 60 playList.add(songList.pop) end |
if
and unless
, both of the loops can also be used
as statement modifiers.
a *= 2 while a < 100 a -= 10 until a < 100 |
file = File.open("ordinal") while file.gets print if /third/ .. /fifth/ end |
third fourth fifth |
$.
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 |
first second third eighth ninth |
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 |
Goodbye |
3.times do print "Ho! " end |
Ho! Ho! Ho! |
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 |
0 1 2 3 4 5 6 7 8 9 |
0.step(12, 3) {|x| print x, " " } |
0 3 6 9 12 |
each
method.
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " } |
1 1 2 3 5 |
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 |
second third |
loop
.
loop { # block ... } |
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).
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 |
songList.each do |aSong| aSong.play end |
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 |
fee fi fo fum 1 2 3 second third |
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 |
Classical Jazz Rock |
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 |
i=0 loop do i += 1 next if i < 3 print i break if i > 4 end |
345 |
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 |
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 } |
0 1 2 3 4 |
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 ] |
prog.rb:4: undefined local variable or method `x' |
x = nil
|
||
y = nil
|
||
[ 1, 2, 3 ].each do |x|
|
||
y = x + 1
|
||
end
|
||
[ x, y ]
|
» |
[3, 4]
|
Previous < |
Contents ^
|
Next >
|