Programming Ruby: The Pragmatic Programmer's Guide

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

# Modules

Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits:

1. Modules provide a namespace and prevent name clashes.
2. Modules implement the mixin facility.

## Namespaces

As you start to write bigger and bigger Ruby programs, you'll naturally find yourself producing chunks of reusable code---libraries of related routines that are generally applicable. You'll want to break this code out into separate files so the contents can be shared among different Ruby programs.

Often this code will be organized into classes, so you'll probably stick a class (or a set of interrelated classes) into a file.

However, there are times when you want to group things together that don't naturally form a class.

An initial approach might be to put all these things into a file and simply load that file into any program that needs it. This is the way the C language works. However, there's a problem. Say you write a set of trigonometry functions `sin`, `cos`, and so on. You stuff them all into a file, `trig.rb`, for future generations to enjoy. Meanwhile, Sally is working on a simulation of good and evil, and codes up a set of her own useful routines, including `beGood` and `sin`, and sticks them into `action.rb`. Joe, who wants to write a program to find out how many angels can dance on the head of a pin, needs to load both `trig.rb` and `action.rb` into his program. But both define a method called `sin`. Bad news.

The answer is the module mechanism. Modules define a namespace, a sandbox in which your methods and constants can play without having to worry about being stepped on by other methods and constants. The trig functions can go into one module:

    module Trig
      PI = 3.141592654
      def Trig.sin(x)
       # ..
      end
      def Trig.cos(x)
       # ..
      end
    end

and the good and bad action methods can go into another:

    module Action
      VERY_BAD = 0
      BAD      = 1
      def Action.sin(badness)
        # ...
      end
    end

Module constants are named just like class constants, with an initial uppercase letter. The method definitions look similar, too: these module methods are defined just like class methods.

If a third program wants to use these modules, it can simply load up the two files (using the Ruby `require` statement, which we discuss on page 103) and reference the qualified names.

    require "trig"
    require "action"

    y = Trig.sin(Trig::PI/4)
    wrongdoing = Action.sin(Action::VERY_BAD)

As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons.

## Mixins

Modules have another, wonderful use. At a stroke, they pretty much eliminate the need for multiple inheritance, providing a facility called a mixin.

In the previous section's examples, we defined module methods, methods whose names were prefixed by the module name. If this made you think of class methods, your next thought might well be ``what happens if I define instance methods within a module?'' Good question. A module can't have instances, because a module isn't a class. However, you can include a module within a class definition. When this happens, all the module's instance methods are suddenly available as methods in the class as well. They get mixed in. In fact, mixed-in modules effectively behave as superclasses.

`module Debug`
`def whoAmI?`
`"#{self.type.name} (\##{self.id}): #{self.to_s}"`
`end`
`end`
`class Phonograph`
`include Debug`
`# ...`
`end`
`class EightTrack`
`include Debug`
`# ...`
`end`
`ph = Phonograph.new("West End Blues")`
`et = EightTrack.new("Surrealistic Pillow")`
``
`ph.whoAmI?` | » | `"Phonograph (#537766170): West End Blues"`
`et.whoAmI?` | » | `"EightTrack (#537765860): Surrealistic Pillow"`

By including the `Debug` module, both `Phonograph` and `EightTrack` gain access to the `whoAmI?` instance method.

A couple of points about the `include` statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called `#include` to insert the contents of one file into another during compilation. The Ruby `include` statement simply makes a reference to a named module. If that module is in a separate file, you must use `require` to drag that file in before using `include`. Second, a Ruby `include` does not simply copy the module's instance methods into the class. Instead, it makes a reference from the class to the included module. If multiple classes include that module, they'll all point to the same thing. If you change the definition of a method within a module, even while your program is running, all classes that include that module will exhibit the new behavior.[Of course, we're speaking only of methods here. Instance variables are always per-object, for example.]

Mixins give you a wonderfully controlled way of adding functionality to classes. However, their true power comes out when the code in the mixin starts to interact with code in the class that uses it. Let's take the standard Ruby mixin `Comparable` as an example. The `Comparable` mixin can be used to add the comparison operators (`<`, `<=`, `==`, `>=`, and `>`), as well as the method `between?`, to a class. For this to work, `Comparable` assumes that any class that uses it defines the operator `<=>`. So, as a class writer, you define the one method, `<=>`, include `Comparable`, and get six comparison functions for free. Let's try this with our `Song` class, by making the songs comparable based on their duration. All we have to do is include the `Comparable` module and implement the comparison operator `<=>`.

    class Song
      include Comparable
      def <=>(other)
        self.duration <=> other.duration
      end
    end

We can check that the results are sensible with a few test songs.

`song1 = Song.new("My Way", "Sinatra", 225)`
`song2 = Song.new("Bicylops", "Fleck", 260)`
``
`song1 <=> song2` | » | `-1`
`song1 < song2` | » | `true`
`song1 == song1` | » | `true`
`song1 > song2` | » | `false`

Finally, back on page 43 we showed an implementation of Smalltalk's `inject` function, implementing it within class `Array`. We promised then that we'd make it more generally applicable. What better way than making it a mixin module?

    module Inject
      def inject(n)
         each do |value|
           n = yield(n, value)
         end
         n
      end
      def sum(initial = 0)
        inject(initial) { |n, value| n + value }
      end
      def product(initial = 1)
        inject(initial) { |n, value| n * value }
      end
    end

We can then test this by mixing it into some built-in classes.

`class Array`
`include Inject`
`end`
`[ 1, 2, 3, 4, 5 ].sum` | » | `15`
`[ 1, 2, 3, 4, 5 ].product` | » | `120`

`class Range`
`include Inject`
`end`
`(1..5).sum` | » | `15`
`(1..5).product` | » | `120`
`('a'..'m').sum("Letters: ")` | » | `"Letters: abcdefghijklm"`

For a more extensive example of a mixin, have a look at the documentation for the `Enumerable` module, which starts on page 403.

### Instance Variables in Mixins

People coming to Ruby from C++ often ask us, ``What happens to instance variables in a mixin? In C++, I have to jump through some hoops to control how variables are shared in a multiple-inheritance hierarchy. How does Ruby handle this?''

Well, for starters, it's not really a fair question, we tell them. Remember how instance variables work in Ruby: the first mention of an ``@''-prefixed variable creates the instance variable in the current object, `self`.

For a mixin, this means that the module that you mix into your client class (the mixee?) may create instance variables in the client object and may use `attr` and friends to define accessors for these instance variables. For instance:

    module Notes
      attr  :concertA
      def tuning(amt)
        @concertA = 440.0 + amt
      end
    end

    class Trumpet
      include Notes
      def initialize(tune)
        tuning(tune)
        puts "Instance method returns #{concertA}"
        puts "Instance variable is #{@concertA}"
      end
    end

    # The piano is a little flat, so we'll match it
    Trumpet.new(-5.3)

produces:

    Instance method returns 434.7
    Instance variable is 434.7

Not only do we have access to the methods defined in the mixin, but we get access to the necessary instance variables as well. There's a risk here, of course, that different mixins may use an instance variable with the same name and create a collision:

    module MajorScales
      def majorNum
        @numNotes = 7 if @numNotes.nil?
        @numNotes # Return 7
      end
    end

    module PentatonicScales
      def pentaNum
        @numNotes = 5 if @numNotes.nil?
        @numNotes # Return 5?
      end
    end

    class ScaleDemo
      include MajorScales
      include PentatonicScales
      def initialize
        puts majorNum # Should be 7
        puts pentaNum # Should be 5
      end
    end

    ScaleDemo.new

produces:

    7
    7

The two bits of code that we mix in both use an instance variable named `@numNotes`. Unfortunately, the result is probably not what the author intended.

For the most part, mixin modules don't try to carry their own instance data around---they use accessors to retrieve data from the client object. But if you need to create a mixin that has to have its own state, ensure that the instance variables have unique names to distinguish them from any other mixins in the system (perhaps by using the module's name as part of the variable name).

## Iterators and the Enumerable Module

You've probably noticed that the Ruby collection classes support a large number of operations that do various things with the collection: traverse it, sort it, and so on. You may be thinking, ``Gee, it'd sure be nice if my class could support all these neat-o features, too!'' (If you actually thought that, it's probably time to stop watching reruns of 1960s television shows.)

Well, your classes can support all these neat-o features, thanks to the magic of mixins and module `Enumerable`. All you have to do is write an iterator called `each`, which returns the elements of your collection in turn. Mix in `Enumerable`, and suddenly your class supports things such as `map`, `include?`, and `find_all?`. If the objects in your collection implement meaningful ordering semantics using the `<=>` method, you'll also get `min`, `max`, and `sort`.

## Including Other Files

Because Ruby makes it easy to write good, modular code, you'll often find yourself producing small files containing some chunk of self-contained functionality---an interface to x, an algorithm to do y, and so on. Typically, you'll organize these files as class or module libraries.

Having produced these files, you'll want to incorporate them into your new programs. Ruby has two statements that do this.

    load "filename.rb"

    require "filename"

The `load` method includes the named Ruby source file every time the method is executed, whereas `require` loads any given file only once. `require` has additional functionality: it can load shared binary libraries. Both routines accept relative and absolute paths. If given a relative path (or just a plain name), they'll search every directory in the current load path (`$:`, discussed on page 140) for the file.

Files loaded using `load` and `require` can, of course, include other files, which include other files, and so on. What might not be obvious is that `require` is an executable statement---it may be inside an `if` statement, or it may include a string that was just built. The search path can be altered at runtime as well. Just add the directory you want to the string `$:`.

Since `load` will include the source unconditionally, you can use it to reload a source file that may have changed since the program began:

    5.times do |i|
       File.open("temp.rb","w") { |f|
         f.puts "module Temp\ndef Temp.var() #{i}; end\nend"
       }
       load "temp.rb"
       puts Temp.var
     end

produces:

    0
    1
    2
    3
    4

Previous < | Contents ^ | Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.