Take the pure object orientation of Smalltalk, but remove the quirky syntax and reliance on a workspace. Add in the convenience and power of Perl, but without all the special cases and magic conversions. Wrap it up in a clean syntax based in part on Eiffel, and add a few concepts from Scheme, CLU, Sather, and Common Lisp. You end up with Ruby.
Thanks in part to the energy of its creator, Yukihiro Matsumoto (Matz), Ruby is already more popular than Python in its native Japan. Ruby is a pure, untyped, object-oriented language just about everything in Ruby is an object, and object references are not typed. People who enjoy exploring different OO programming paradigms will enjoy experimenting with Ruby: It has a full metaclass model, iterators, closures, reflection, and supports the run-time extension of both classes and individual objects.
The freely available Ruby (http://www.ruby-lang.org/) is being used worldwide for text processing, XML and web applications, GUI building, in middle-tier servers, and general system administration. Ruby is used in artificial intelligence and machine-learning research, and as an engine for exploratory mathematics.
Ruby's simple syntax and transparent semantics make it easy to learn. Its direct execution model and dynamic typing let you develop code incrementally: You can typically add a feature and then try it immediately, with no need for scaffolding code. Ruby programs are typically more concise than their Perl, Python, or C++ counterparts, and their simplicity makes them easier to understand and maintain. When you bump up against some facility that Ruby is lacking, you'll find it easy to write Ruby extensions using both Ruby and low-level C code that adds new features to the language.
We came across Ruby when we were looking for a language to use as a prototyping and specification tool. We've used it on all of our projects since. We have Ruby code performing distributed logging, executing within an X Windows window manager, precompiling the text of a book, and generating indexes. Ruby has become our language of choice.
Everything's an Object
Everything you manipulate in Ruby is an object, and all methods are invoked in the context of an object. (In our examples here, we'll sometimes show the result of evaluating an expression to the right of an arrow (->). This is not part of the Ruby syntax.)
"gin joint".length -> 9 "Rick".index("c") -> 2 -1942.abs -> 1942 sam.play(aSong) -> "duh dum, da dum de dum ..."
In Ruby and Smalltalk jargon, all method calls are actually messages sent to an object. Here, the thing before the period is called the "receiver," and the name after the period is the method to be invoked.
The first example asks a string for its length, and the second asks a different string to find the index of the letter "c." The third line has a number calculate its absolute value. Finally, we ask the object "sam" to play us a song. It's worth noting a major difference between Ruby and most other languages. In Java, for example, you'd find the absolute value of some number by calling a separate function and passing in that number. In Ruby, the ability to determine absolute values is built into numbers they take care of the details internally. You simply send the message abs
to a number
object and let it do the work.
number = Math.abs(number) // Java number = number.abs // Ruby
The same applies to all Ruby objects: In C, you'd write strlen(name)
; while in Ruby, it's name.length
. This is part of what we mean when we say that Ruby is a genuine OO language.
The parentheses on method calls are optional unless the result would be ambiguous. This is a big win for parameterless methods, as it cuts down on the clutter generated by all those () pairs.
Classes and Methods
As Example 1 shows, Ruby class definitions are remarkably simple: The keyword class
is followed by a class name, the class body, and the keyword end
to finish it all off. Ruby features single inheritance: Every class has exactly one superclass, which can be specified as in Example 2. A class with no explicit parent is made a child of class Object
the root of the class hierarchy and the only class with no superclass. If you're worried that a single inheritance model just isn't enough, never fear. We'll be talking about Ruby's mix-in capabilities shortly.
class Song def initialize(title) @title = title end def to_s @title end end aSong = Song.new("My Way")
Example 1: A simple class definition.
class KaraokeSong < Song def initialize(title, lyric) super(title) @lyric = lyric end def to_s super + " [#@lyric]" end end
Example 2: A subclass of class Song.
Returning to the definition of class Song
in Example 1, the class contains two method definitions, initialize
and to
_s
. The initialize
method participates in object construction. To create a Ruby object, you send the message new
to the object's class, as in the last line of Example 1. This new
message allocates an empty, uninitialized object, and then sends the message initialize
to that object, passing along any parameters that were originally given to new
. This makes initialize
roughly equivalent to constructors in C++ and Java.
Class Song
also contains the definition of the method to_s
. This is a convenience method; Ruby sends to_s
to an object whenever it needs to represent that object as a string. By overriding the default implementation of to_s
(which is in class Object
), you get to control how your objects are printed (for example, by tracing statements and the debugger), and when they are interpolated in strings. In Example 2, we create a subclass of class Song
, overriding both the initialize
and to_s
methods. In both of the new methods we use the super
keyword to invoke the equivalent method in our parent class. In Ruby, super
is not a reference to a parent class; instead, it is an executable statement that reinvokes the current method, skipping any definition in the class of the current object. By default, all methods (apart from initialize
) are publicly accessible; they can be invoked by anyone. Ruby also supports private and protected access modifiers, which can be used to restrict the visibility of methods to a particular object or a particular class, respectively. Ruby's implementation of "private" is interesting: You cannot invoke a private method with an explicit receiver, so it may only be called with a receiver of self,
the current object.
Attributes, Instance Variables, and Bertrand Meyer
The initialize
method in class Song
contains the line @title = title
. Names that start with single "at" signs (@) are instance variables variables that are specific to a particular instance or object of a class. In our case, each Song
object has its own title, so it makes sense to have that title be an instance variable. Unlike languages such as Java and C++, you don't have to declare your instance variables in Ruby; they spring into existence the first time you reference them. Another difference between Ruby and Java/C++ is that you may not export an object's instance variables; they are available to subclasses, but are otherwise inaccessible. (This is roughly equivalent to Java's "protected" concept.) Instead, Ruby has attributes: methods that get and set the state of an object. You can either write these attribute methods yourself, as in Example 3, or use the Ruby shortcuts in Example 4.
class Song # ... def title # attribute reader @title # returns instance variable end def title=(title) # attribute setter @title = title end end
Example 3: Writing your own attribute methods.
class Song # ... attr_accessor :title end
Example 4: Ruby shortcut for attribute methods.
It's interesting to note the method called title=
in Example 3. The equals sign tells Ruby that this method can be assigned to it can appear on the left side of an assignment statement. If you were to write aSong.title = "Chicago,"
Ruby translates it into a call to the title=
method, passing "Chicago"
as a parameter. This may seem like some trivial syntactic sugar, but it's actually a fairly profound feature. You can now write classes with attributes that act as if they were variables, but are actually method calls. This decouples users of your class from its implementation you're free to change an attribute back and forth between some algorithmic implementation and a simple instance variable. In Object-Oriented Software Construction (Prentice Hall, 2000), Bertrand Meyer calls this the "Uniform Access Principle."
Blocks and Iterators
Have you ever wanted to write your own control structures, or package up lumps of code within objects? Ruby's block construct lets you do just that. A block is simply a chunk of code between braces, or between do
and end
keywords. When Ruby comes across a block, it stores the block's code away for later; the block is not executed. In this way, a block is similar to an anonymous method. Blocks can only appear in Ruby source alongside method calls.
A block associated with a method call can be invoked from within that method. This sounds innocuous, but this single facility lets you write callbacks and adaptors, handle transactions, and implement your own iterators. Blocks are also true closures, remembering the context in which they were defined, even if that context has gone out of scope.
The method in Example 5 implements an iterator that returns successive Fibonacci numbers (the series that starts with two 1s, where each term is the sum of the two preceding terms). The main body of the method is a loop that calculates the terms of the series. The first line in the loop contains the keyword yield
, which invokes the block associated with the method, in this case passing as a parameter the next Fibonacci number. When the block returns, the method containing the yield resumes. Thus, in our Fibonacci example, the block will be invoked once for each number in the series until some maximum is reached.
def fibUpTo(max) n1, n2 = 1, 1 while n1 <= max yield n1 # invoke block value n1, n2 = n2, n1+n2 # and calculate next end end
Example 5: Iterator for Fibonacci numbers.
Example 6 shows this in action. The call to fibUpTo
has a block associated with it (the code between the braces). This block takes a single parameter the name between the vertical bars at the start of the block is like a method's parameter list. The body of the block simply prints this value.
fibUpTo(1000) {|term| print term, " "} produces: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
Example 6: Using the fibUpTo iterator.
If you write your own collection classes (or any classes that implement a stream of values), you can benefit from the real beauty of Ruby's iterators. Say you've produced a class that stores objects in a singly linked list. The method each
in Example 7 traverses this list, invoking a block for each node. This is a Visitor Pattern in three lines of code. The choice of the name, each
, was not arbitrary. If your class implements an each
method, then you can get a whole set of other collection-oriented methods for free, thanks to the Enumerable
mix-in.
class LinkedList # ... def each node = head while node yield node node = node.next end end end
Example 7: Iterator for a linked list.