Three Books About Ruby

Paul R. Potts

I realize that reviews of technical books have an inherently limited audience, but I have reviwed a number of them over the years, and so wanted to include at least one such review in this collection.

I have never used Ruby to write a large project, but I have used to write some small programs over the years, of the type needed to solve a particular, one-off problem, such as generating or transforming test data.

Ruby is all right. I find that I like it more than Python, because it has better support for functional programming idioms, but in general, I prefer languages that help with type safety. I don’t particularly like the “duck typing” concept, preferring “interfaces” or “mix-ins” using multiple inheritance. Type inference has come a long way over the years, and languages like Haskell can now often do amazing work deriving types and enforce them, even if you don’t specify types in your code.

If I recall correctly, after I posted this review, someone from O’Reilly got in touch with me to apologize for Ruby in a Nutshell. I don’t recall the details, but it was gratifying to have my comments noticed.

Reviewed in this article:

  1. Yukihiro Matsumoto: Ruby in a Nutshell (O’Reilly, 2002: 204 pages)
  2. Hal Fulton: The Ruby Way (Sams, 2002: 579 pages)
  3. Dave Thomas and Andrew Hunt: Programming Ruby: The Pragmatic Programmer’s Guide (Addison-Wesley, 2001: 564 pages)

First she tried Papa Bear’s porridge, but it was too hot.
Then she tried Mama Bear’s porridge, but it was too cold.
Then she tried Baby Bear’s porridge, and it was just right, so she ate it all up.

Let’s suppose that you’re an experienced programmer who wants to learn Ruby, or a Ruby programmer who needs a reference. Which of these three books is “just right?”

Ruby in a Nutshell

Let’s begin by looking at Ruby in A Nutshell. O’Reilly books tend to have a good reputation, and I’ve been quite pleased with a number of them, including several other books in the Nutshell line such as Java in a Nutshell. Therefore, it is with considerable disappointment that I’m forced to report that Ruby in a Nutshell is not only the poorest book I’ve seen in the Nutshell line, but also the poorest O’Reilly book I’ve ever examined. Obviously, as Goldilocks would say, this one is “too cold!”

To find out why, let’s begin with the code sample on page 2:

ary = [1,2,3,4,5]
ary.each do |i|
   puts 1*2
end   # prints 2,3,4,8,10 for each line

Not only is the comment wrong (this example should print 2, 4, 6, 8, and 10, once each), but the code has a bug: the puts statement contains a “1” instead of an “i.” That’s not inspiring: I’m not sure who to blame, but clearly the editors or technical reviewers did not try the code samples as printed. Let’s go on to the next example:

ary = [1,2,3,4,5]
ary = ary.select do |i|
   i %2 == 0
end   # returns array of even numbers

This example also fails! Why? Because Ruby is sensitive to whitespace in certain constructs: the lack of a space between the “%” and the “2” causes trouble. That’s two critical typos in two brief examples: not a promising start! Let’s move on to the next example:

begin
   f = open(path)
rescue
   puts "#{path} does not exist."
   exit 1
end

This example does not execute, because “path” is undefined. That’s a trivial omission, obviously, but again it indicates that no one bothered to make these short examples runnable. That’s disappointing.

The next example shows how to use a network socket to retrieve the time:

require "socket"
print TCPSocket.open("localhost", "daytime").gets

This also does not work on my system (running MacOS X). The reason is because my system is not running an appropriate server; I know that, but if I was more of a novice, I might have given up Ruby in disgust by this point (on page 3), mistakenly blaming the language or library implementation. Given that Yukihiro Matsumoto is the author of Ruby, that’s obviously not a desirable outcome.

Keep in mind that we are on page 3 of this book, in the introduction, under a heading entitled “Ruby’s Elegance.” This Nutshell book is clearly not intended as a beginner’s tutorial, but does this principle make it acceptable that four of the five code samples in this section do not run as presented? I don’t think it does. I think this is just the first warning indication of the disastrous lack of quality in this book. The introduction trails off with a few additional examples, including a minor variation on the socket example already presented and a redundant example of exception handling, but with no further commentary. This entire introductory chapter seems not to whet the appetite to learn Ruby as much as ruin it. However, let’s press on and see if it gets better.

The book is quite short (with 204 numbered pages), and that’s good. I like short books; as the introduction to Kerninghan and Ritchie’s widely-known book The C Programming Language says, a small language “is not well served by a big book.” The second chapter, “Language Basics,” weighs in at just 30 pages. But it turns out that it isn’t the length, it is the organization and presentation of the content. The other Nutshell books do it well; Kerninghan and Ritchie’s book does it well. Ruby in a Nutshell does not.

The chapter begins with a description of the command-line options to the Ruby executable and a list of the environment variables that Ruby recognizes. These things would be more properly placed in an appendix devoted to the implementation. We are now left with just 27 to pages to cover the core language. Let me point out a few specific examples that show how the text veers off track.

Under the heading “Literals” and sub-heading “Numbers,” we are shown the examples “123,” a decimal number, “0377,” an octal number, and “0xff,” a hexadecimal number. So far, so good. But included in the list is “1_234,” listed as “decimal with underline.” What does this mean? What is the purpose of writing a decimal number with an embedded underline? (No mention is made of “underline” or “underscore” in the index, and the text offers no clue). That’s a very minor example, but it is telling.

Aside: it turns out this is a feature borrowed from Perl, and it is a separator that does not change the meaning of the number but which can be put in just for readability, the way that numbers like 1,000,000 are often written using commas. But I’m not really a Perl expert, and the text should not assume the reader is.

Under “Strings,” we’re shown the difference between double-quoted and single-quoted strings. On the very next page, though, we see an example of a string in backquotes. No mention is made of backquotes in the index, and backquoted strings are never explained.

I’ve programmed in Scheme and other languages that support a symbol type; I thought I knew what a symbol was. Under “Symbols,” we read that “A symbol is an object corresponding to an identifier or variable.” In fact, that’s false. A symbol never corresponds to a variable. A symbol is generally made of a string, hashed to a unique object, and usable as a distinguished value.

We are shown two examples:

:foo

is described, in a code comment, as

# symbol for 'foo'

while

:$foo

is described as

# symbol for variable '$foo'

What this really means is that the first symbol is constructed out of a plain identifier, and the second out of an identifier that is a global variable name. Any string can be used to generate a symbol. You can make a symbol out of a string that contains spaces, using syntax like this:

:"string with spaces"

but this is not shown. You can also do it with the Intern method; including a mention of that method here would reassure the user that symbols in Ruby work pretty much like symbols in other languages that support them. Instead, the book is riddled with bizarre and confusing terminology. Matsumoto may not be a native speaker, but he credits editors and reviewers; were they asleep on the job?

On page 16 under “Assigment,” we read “The following elements may assign targets.” What does that phrase mean? What is an “element?” Why was this text not turned into something more clear, using standard terminology? Under “Operators,” we read “most operators are in fact method calls.” Then, three lines later, we read “Most operators are actually method calls.” Why was this redundancy left in place? Copy-editing should have caught this; this book is very, very poorly edited.

On the next page, under “Range operators,” the description of the form “expr1 … expr2” is “Evaluates expr2 on the iteration after expr1 turns true.” Range operators are incredible useful and clever; it is a shame that anyone reading this description will be unlikely to gain any insight into how to use them. In a sidebar on special versions of methods that have “!” or “?” appended (in the Lisp world, methods ending in “!” are generally known as destructive, and methods ending in “?” are generally known as predicates), we read “A question mark ? is appended to a method that determines the state of a Boolean value, true or false.” Why wasn’t this convoluted sentence edited to say “method that returns a Boolean value?” In the next sentence, we learn that “Attempting to call a method without specifying either its arguments or parentheses in a context in which a local variable of the same name exists results in the method call being interpreted as a reference to the local variable, not a call to the method.” This sentence needs at least six prepositional phrases to tell us that a local variable with the same name as a method can shadow the method, if the method call is ambiguous; an brief example here would be worth far more than this convoluted description.

As a further case study, let’s take a look at the presentation of one more language construct: Ruby’s version of C’s switch, in Ruby called case. There are a few key points to keep in mind about the way Ruby implements this construct. The first is that there is no default fall-through, as in C and C++, and hence no need for break statements — in fact, break may not be used here. The second is that the clauses are evaluated in the order given: this can be important, since clauses may overlap (for example, if they use ranges). The third is that to determine if a clause matches, the === operator is used, in a less-than-obvious way (briefly, if a and b are not the same type, then “a === b” does not necessarily imply “b === a.” I’ll show an example of this later).

Any Ruby reference worth the name should mention these three key points. In Ruby in a Nutshell, we are given a BNF skeleton (it is not quite real BNF, though) and a few lines of description. The lack of default fall-through is not mentioned. The use of === is mentioned, but without any detail. The fact that Ruby always evaluates the cases in the listed order is not mentioned. As far as I’m concerned, this presentation of the case statement is close to useless; it could serve to remind someone who already knows Ruby of the syntactical form, like a pocket reference card, but gives almost nothing of the distinguishing semantics for a programmer coming from another language.

I want to look at one more example: the use of while and until as statement modifiers. This is a clever little language feature that lets you write code like this:

greeting = true
print "Hello\n" while greeting

The above code will say “Hello” forever, while this version:

greeting = false
print "Hello\n" while greeting

never says “Hello.” It is a somewhat non-obvious detail that when you use while as a modifier at the end a single statement, the condition is evaluated first. This runs contrary to C’s do loop form. You can also use while at the end of a block of code:

greeting = false
begin
   print "Hello\n"
end while greeting

but in this case, “Hello” will be printed once; the block is executed once before the condition is evaluated, just like C’s do loop.

It is important to me that a book on Ruby should make a clear distinction between these two modes of behavior; Ruby in a Nutshell fails to clearly differentiate the behavior of the single-statement form. Instead we get the terse description “executes code while condition is true” for the single-statement form, and this awful sentence for the second: “If a while modifier follows a begin statement with no rescue or ensure clauses, code is executed once before conditional is evaluated.” It is probably technically correct, but at this point in the exposition, it does not clarify anything to bring in rescue and ensure without further elaboration. I should point out that the other two books I’ll discuss do explicitly mention this behavior, albeit not necessarily in a prominent way.

By comparison, although it is not billed as a tutorial, Python in a Nutshell devotes 39 pages to the Python language, and the chapter is immeasurably more readable and more complete. It is also possible to divine all the tricky parts of Java from Java in a Nutshell; I refer to this book frequently when I’ve forgotten a detail about, say, inner classes. That just doesn’t seem possible with this book. Is this because Ruby is so irregular, with an enormous number of special “quirks?” I don’t believe so; Python and Java are certainly “quirky” in places as well (just look at the semantics of bindings from within nested scopes in Python, or the syntax of anonymous inner classes in Java). In a language guide, though, it is essential that any irregular semantics be clearly pointed out, and Ruby in a Nutshell doesn’t do this well.

The remainder of the book covers the built-in library (built-in to the interpreter), and the standard library (defined in separately importable modules). This material looks concise (in fact, too concise). For example, suppose I wanted to fix the example given above:

require "socket"
print TCPSocket.open("localhost", "daytime").gets

On page 113, we can find the entry for TCPSocket. It is less than a page in length, and most of the entry is taken up by a longish piece of sample code, and most of that sample is scaffolding. (Again, it isn’t the space used, but how it is used). The sample code uses the spelling TCPsocket (note the altered capitalization: another typographical error, but fortunately names in Ruby are not case-sensitive). The example also uses ARGV and thus, as far as I can tell, can’t be executed using irb (the command-line Ruby interactor). It uses two additional methods on TCPsocket: addr and peeraddr, but these are not listed in the class methods. It turns out the methods are part of IPSocket, which is the superclass of TCPSocket, so I turned to the entry on IPSocket. The example given for IPSocket is actually an example of how to use TCPSocket (this time with the expected capitalization). This example succeeds in opening an HTTP connection to http://www.ruby-lang.org. It works, and that’s a start, but it’s too late to do much to enhance my opinion of Ruby in a Nutshell. It doesn’t have to be this way; as I’ll show, other authors manage to convey far more useful information in even less space.

For comparison, let’s now examine Hal Fulton’s book.

The Ruby Way

Fulton’s book is much wordier; his text is full of digression, but, more importantly, his text is full of working examples. Confusingly, when he is giving an abstract example to show a form, rather than a piece of executable Ruby code, such as:

case expression
   when value
      some_action
end

This abstract example is not set off in italics or boldface or BNF. In practice, it is usually clear when he is showing an abstract example, but this could be improved. On the bright side, every concrete code example I tried worked perfectly. When Fulton describes Ruby’s case statement (p. 55), he provides an example of how the definition of the === method on the string class can confuse you.

case /Hell/
   when "Hello"
      print "We matched.\n"
   else
      print "We didn't match.\n"
end

As you might expect after that build-up, the code fragment above prints “We didn’t match.” Fulton also spends some time explaining the absence of the break statement and lack of default fall-through, and mentions not only that the case “limbs” are evaluated in sequence, but also how the expressions in limbs that are not reached are never evaluated. The key concepts are included, but the result of this wordiness is that Fulton takes nearly 3 pages to explain the case statement.

Would you like to see cookbook examples of using Ruby with strings and queues? Fulton’s got them. Did you want to know how to use Ruby to check whether a graph is fully connected? How about whether that graph has a Euler circuit? Fulton’s got an example. Sometimes his book veers towards a cookbook, but depending on your needs and skill level, these worked-out examples may be just what you need. While I can live without information on the Ruby/Tk and Ruby/GTK libraries, I find the material on threads to be very useful. Ruby has very powerful and concise support for threads, but threads can be hard to understand in any language, and clear examples of how to use them are most welcome. Chater 8, on scripting, is not currently of much interest to me, but another reader might find a great deal of value in using Ruby to write scripts for system administration tasks.

Continuing our example, let’s see how Fulton does with his coverage of TCPSocket. First of all, TCPSocket is not in the index. That’s a bad sign. TCPServer is, though, and his server example also includes a client that uses uses TCPSocket. Let’s see if his example works (from page 420):

require "socket"

PORT = 12321
HOST = ARGV[0] || 'localhost'

server = UDPSocket.open   # Using UDP here...
server.bind(nil, PORT)

loop do
   text, sender = server.recvfrom(1)
   server.send(Time.new.to_s + "\n", 0, sender[3], sender[1])
end

And now Fulton’s client code, which I will execute in a different terminal window using another instance of irb:

require "socket"
require "timeout"

PORT = 12321
HOST = ARGV[0] || 'localhost'

socket = UDPSocket.new
socket.connect(HOST, PORT)

socket.send("", 0)
timeout(10) do
   time = socket.gets
   puts time
end

And, in fact, it worked perfectly on the first try. There is something in the level of detail in Fulton’s writing that inspires confidence that he has tested his code. From there on, Fulton illustrates a threaded server, and then provides a larger example: a chess server. I have not tested it yet, but so far I have reason to believe that his examples and explanation are both top-notch.

The book does have its weaknesses: the index, as I’ve mentioned, is not comprehensive, and the typography is also somewhat bland. There are no diagrams to be found, even when one would be useful (for example, in explaining what an Euler circuit is). This book fits my tastes well, but some may consider it “too hot,” though: too wordy, and too example-heavy to be the reference I discussed earlier. I think it is great material for any Ruby developer, but is it the best book learning and reference use? To answer that, let’s take a look at one more.

Programming Ruby: The Pragmatic Programmer’s Guide

If you are interested in reading this book on-line (I usually prefer a paper copy), you can actually find the complete text located here: http://www.rubycentral.com/book/index.html, and all the sample code is available here: http://www.pragmaticprogrammer.com/ruby/samples/index.html.

The book starts out well: we’ve got notation conventions! A roadmap! This book has something resembling a recursive structure: different aspects of Ruby are discussed (briefly), then discussed later in much more depth, and sometimes again in close-up. I like that approach. It means we can get into examples quickly. By page ten we’re into arrays and hashes, and then on to a brief survey of control structures. Let’s take a look at an example (page 11):

if counts > 10
   puts "Try again"
elsif tries == 3
   puts "You lose"
else
   puts "Enter a number"
end

Once again, we’ve been given a non-working example: the variables “counts” and “tries” are not defined. In case it isn’t clear yet, I really don’t like non-working examples in programming books. If an example is abstract, use BNF or italicized words; if it is concrete, put in the extra effort into testing and make a usable example. Your opinion on this matter may not be the same, and to the author’s credit, it does appear that most later examples in the text are runnable; indeed, they are presented as brief interactive sessions.

Also, if look at the online version of the book’s code, you’ll find that the code for page 11 has a link called “See hidden code.” This link will show the extra lines that make the sample runnable. This seems a bit silly, especially since most of the samples are quite short and including the extra lines in the print version would not have added significantly to the page count. But if you are like me and want to try out the samples as you read them, you might want to follow along on the web site as you read.

After the brief overview chapter, we’re right into classes, objects and variables. We work through an extended, semi-real-world example that shows the definition of a toy class and how to play with it. (I don’t use the term “toy” here in a disparaging way; tutorial material usually benefits from somewhat scaled-down examples so that the reader can focus on the mechanics, and not the problem domain).

The typography is excellent in this book, yielding a very clean look; arrows in the code samples indicate the results that are returned from various expressions. Diagrams are occasionally used, for example to show how references may be reassigned and objects aliased. This dynamism may be unfamiliar to programmers from coming to Ruby from, for example, C or Pascal (although Java programmers will be familiar with this mode of programming).

Another aspect of this book that I like is the occasional interjection of comparisons with other languages. It is particularly illuminating to discuss Ruby’s code blocks this way. A bit like Scheme’s closures, code blocks can be difficult for static-language programmers to understand. Programmers coming from languages that don’t support higher-order functions may find themselves dismissive of such features. Of course, just about any language feature can be considered “irrelevant” — why use classes, when we can simulate them with structures? Closures can be used to implement “lightweight” callbacks and maintain state without the need to define extra classes and implement them; they provide a different, highly useful abstraction. To use Ruby to its full potential, you should understand closures, and thus code blocks; this book can help. (Just don’t ask me to explain my other favorite programming technique, borrowed from Dylan and Lisp-family languages: currying).

So what about that example given in Ruby in a Nutshell showing an integer containing underscores? In this book, there’s an example, and the comment says “underscore ignored.” Of course, there is still no “underscore” entry in the index, but at least I got my answer, and it is the answer I expected.

Now, how about that case statement again? This book covers it initially in just over a page; it shows several different (again, unfortunately, non-working) examples illustrating syntactic variations. It points out the use of the === operator, but it does not describe the order of evaluation, short-circuiting, the lack of default fall-through, or the specific issue likely to arise when using regular expressions as the the target of the comparison expressions. This makes the explanation a little bit weak; you’d better hope to follow the examples here, because the text will not help you much.

But wait! Chapter 18 covers the language again, in yet more depth. This is almost a complete formal treatment, and it is most welcome. Here we learn that case evaluates expressions in order, that it does short-circuiting, how the else clause works, and the additional tidbit that if no case “limb” is chosen, and no else clause is present, the whole expression will silently return nil. And, especially in comparison to Ruby in a Nutshell, he exposition here is crystalline. So we do get just about everything that we need.

The second half of the book is a major reference section, and it is excellent. The layout is very usable, and very short interactive examples are embedded in most of the entries to show how they work. This is an extremely helpful approach. In format, this section is a bit like The Java Almanac. The authors provide an example (I can’t quite reproduce their typography, but here is the code):

require "socket"
t = TCPSocket.new('localhost', 'ftp')
t.gets
t.close

This example did not work, at first, because I’m not running an FTP server; enabling the built-in FTP server fixed the problem.

So, is this book “just right?” It seems to be pretty close. If you buy just one of these Ruby books, this one should be it, because it is concise and contains both good tutorial and reference material; in fact, it is pretty much everything that Ruby in a Nutshell should have been, in a slightly wordier form. Sadly, Ruby does not yet have a book that is as concise and elegant as the language itself.

On the other hand, if you can buy two books, and really want to get into what Fulton calls “the Ruby way,” and see lots of idiomatic Ruby code, and you don’t mind extended cookbook examples and a more expository writing style, I would highly recommend that you buy this book together with Fulton’s book. Between the two of them, you should be off and running. As for Ruby in a Nutshell: it seems to be a terrible anomaly in what are generally good O’Reilly books. I only wish I had saved my receipt, and not scribbled corrections and annotations all over the pages in frustration, so that I could get my money back. I also hope O’Reilly will redeem themselves as a publisher with an improved second edition.

Ann Arbor, Michigan
November 5, 2003

Creative Commons Licence
This work by Paul R. Potts is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The CSS framework is stylize.css, Copyright © 2014 by Jack Crawford.

Article IndexWriting Archive