Blocks and Closures
Ruby blocks can be converted into objects of class Proc
. These Proc
objects can be stored in variables and passed between methods just like any other object. The code in the corresponding block can be executed at any time by sending the Proc
object the message call
.
Ruby Proc
objects remember the context in which they were created: the local variables, the current object, and so on. When called, they recreate this context for the duration of their execution, even if that context has gone out of scope. Other languages call Proc
objects closures.
The following method returns a Proc
object:
def times(n) return Proc.new {|val| n * val} end
The block multiplies the method's parameter, n
, by another value, which is passed to the block as a parameter. The following code shows this in action:
double = times(2) double.call(4) -> 8 santa = times("Ho! ") santa.call(3) -> "Ho! Ho! Ho! "
The parameter n
is out of scope when the double
and santa
objects are called, but its value is still available to the closures.
Modules, Mix-ins, and Multiple Inheritance
Modules are classes that you can't instantiate: You can't use new
to create objects from them, and they can't have superclasses. At first, they might seem pointless, but in reality, modules have two major uses.
- Modules provide namespaces. Constants and class methods may be placed in a module without worrying about their names conflicting with constants and methods in other modules. This is similar to the idea of putting static methods and variables in a Java class. In both Java and Ruby you can write
Math.PI
to access the value of p (although in Ruby,PI
is a constant, rather than a final variable, and you're more likely to see the notationMath::PI
). - Modules are also the basis for mix-ins, a mechanism by which you add canned behavior to your classes.
Perhaps the easiest way to think about mix-ins is to imagine that you could write code in a Java interface. Any class that implemented such an interface would receive not just a type signature; it would receive the code that implemented that signature as well. We can investigate this by looking at the Enumerable
module, which adds collection-based methods to classes that implement the method each
. Enumerable
implements the method find
(among others). find
returns the first member of a collection that meets some criteria. This example shows find
in action, looking for the first element in an array that is greater than four.
[1,3,5,7,9].find {|i| i > 4 } -> 5
Class Array
does not implement the find
method. Instead, it mixes in Enumerable
, which implements find
in terms of Array
's each
method; see Example 8. Contrast this approach with both Java and C#, where it is up to the class implementing the collection to also provide a considerable amount of supporting scaffolding.
module Enumerable def find each {|val| return val if yield(val)} end end class Array include Enumerable end
Example 8: Adding functionality with a mix-in.
Although a class may have only one parent class (the single inheritance model), it may mix in any number of modules. This effectively gives Ruby the power of multiple inheritance without some of the ambiguities that can arise. (And in cases where mixing in modules would cause a name clash, Ruby supports method renaming.)
Other Good Stuff
Other Ruby highlights include:
- Classes and modules are never closed. You can add to and alter all classes and modules (including those built into Ruby itself).
- Dynamic loading. Ruby modules (both source and binary) may be loaded dynamically, both explicitly and on demand.
- Reflection. As well as supporting reflection into both classes and individual objects, Ruby lets you traverse the list of currently active objects.
- Marshaling. Ruby objects can be serialized and deserialized, allowing them to be saved externally and transmitted across networks. A full distributed-object system, DRb, is written in about 200 lines of Ruby code.
- Libraries. Ruby has a large (and growing) collection of libraries. All major Internet protocols are supported, as are most major databases. Extending Ruby is simple compared to adding extensions to Perl.
- Threads. Ruby has built-in support for threads, and doesn't rely on the underlying operating system for thread support.
- Object specialization. You can add methods to individual objects, not just classes. This is useful when defining specialized behavior for objects (for example, determining their response to GUI events).
- Exceptions. Ruby has a fully object-oriented, extensible exception model.
- Garbage collection. Ruby objects are automatically garbage collected using a mark-and-sweep algorithm. The choice of mark-and-sweep simplifies programming and makes writing extensions easier (no reference counting problems).
- Active developer community. The Ruby development community is still a bazaar: small, intimate, and bustling. Changes are discussed openly and are made efficiently.