Modules

Modules serve two purposes:

  • as namespaces for defining other types, methods and constants
  • as partial types that can be mixed in other types

An example of a module as a namespace:

  1. module Curses
  2. class Window
  3. end
  4. end
  5. Curses::Window.new

Library authors are advised to put their definitions inside a module to avoid name clashes. The standard library usually doesn’t have a namespace as its types and methods are very common, to avoid writing long names.

To use a module as a partial type you use include or extend.

An include makes a type include methods defined in that module as instance methods:

  1. module ItemsSize
  2. def size
  3. items.size
  4. end
  5. end
  6. class Items
  7. include ItemsSize
  8. def items
  9. [1, 2, 3]
  10. end
  11. end
  12. items = Items.new
  13. items.size # => 3

In the above example, it is as if we pasted the size method from the module into the Items class. The way this really works is by making each type have a list of ancestors, or parents. By default this list starts with the superclass. As modules are included they are prepended to this list. When a method is not found in a type it is looked up in this list. When you invoke super, the first type in this ancestors list is used.

A module can include other modules, so when a method is not found in it it will be looked up in the included modules.

An extend makes a type include methods defined in that module as class methods:

  1. module SomeSize
  2. def size
  3. 3
  4. end
  5. end
  6. class Items
  7. extend SomeSize
  8. end
  9. Items.size # => 3

Both include and extend make constants defined in the module available to the including/extending type.

Both of them can be used at the top level to avoid writing a namespace over and over (although the chances of name clashes increase):

  1. module SomeModule
  2. class SomeType
  3. end
  4. def some_method
  5. 1
  6. end
  7. end
  8. include SomeModule
  9. SomeType.new # OK, same as SomeModule::SomeType
  10. some_method # OK, 1

extend self

A common pattern for modules is extend self:

  1. module Base64
  2. extend self
  3. def encode64(string)
  4. # ...
  5. end
  6. def decode64(string)
  7. # ...
  8. end
  9. end

In this way a module can be used as a namespace:

  1. Base64.encode64 "hello" # => "aGVsbG8="

But also it can be included in the program and its methods can be invoked without a namespace:

  1. include Base64
  2. encode64 "hello" # => "aGVsbG8="

For this to be useful the method name should have some reference to the module, otherwise chances of name clashes are high.

A module cannot be instantiated:

  1. module Moo
  2. end
  3. Moo.new # undefined method 'new' for Moo:Module

Module Type Checking

Modules can also be used for type checking.

If we define two modules with names A and B:

  1. module A; end
  2. module B; end

These can be included into classes:

  1. class One
  2. include A
  3. end
  4. class Two
  5. include B
  6. end
  7. class Three < One
  8. include B
  9. end

We can then type check against instances of these classes with not only their class, but the included modules as well:

  1. one = One.new
  2. typeof(one) # => One
  3. one.is_a?(A) # => true
  4. one.is_a?(B) # => false
  5. three = Three.new
  6. typeof(three) # => Three
  7. three.is_a?(A) # => true
  8. three.is_a?(B) # => true

This allows you to define arrays and methods based on module type instead of class:

  1. one = One.new
  2. two = Two.new
  3. three = Three.new
  4. new_array = Array(A).new
  5. new_array << one # Ok, One includes module A
  6. new_array << three # Ok, Three inherits module A
  7. new_array << two # Error, because Two neither inherits nor includes module A