Programming Ruby: The Pragmatic Programmer's Guide

# Programming Ruby ### The Pragmatic Programmer's Guide
Previous < | Contents ^ | Next >

# Expressions

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.

## Operator Expressions

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:

    aSong[0, 0.15].play

## Miscellaneous Expressions

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.

### Command Expansion

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 Nov 25 23:43:12 CST 2001\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 `$?`.

### Backquotes Are Soft

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 Nov 25 23:43:12 CST 2001
    prog.rb:3: command not found: data
    prog.rb:5:in ``': Command data failed (RuntimeError)
    	from prog.rb:10

## Assignment

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.

### Parallel Assignment

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

`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]

### Nested Assignments

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.

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

### Other Forms of 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.

`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*`

## Conditional Execution

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.

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

### Defined?, And, Or, and Not

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/`.

### If and Unless Expressions

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

### If and Unless Modifiers

Ruby shares a neat feature with Perl. Statement modifiers let you tack conditional statements onto the end of a normal statement.

    mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
    puts "a = #{a}" if fDebug
    print total unless total == 0

For an `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

Because `if` itself is an expression, you can get really obscure with statements such as:

    if artist == "John Coltrane"
      artist = "'Trane"
    end unless nicknames == "no"

This path leads to the gates of madness.

## Case Expressions

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

## Loops

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

produces:

    Goodbye

### Iterators

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

### For ... In

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

produces:

    Classical Jazz Rock

### Break, Redo, and Next

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

produces:

    345

### Retry

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
    }

produces:

    0 1 2 3 4

## Variable Scope and Loops

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:0x401c0ce0> (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.

`x = nil`
`y = nil`
`[ 1, 2, 3 ].each do |x|`
`y = x + 1`
`end`
`[ x, y ]` | » | `[3, 4]`

Previous < | Contents ^ | Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0.
This reference is available for download.