In the first part, we learned that a module was a namespace, a way to regroup similar things to help us organize our application better and to avoid name clashes. It was rather easy to understand because we didn’t speak about the true power that resides in modules.
Other than a namespace, what is a module?
A module is like a class with 3 key differences :
- It is not a class (… DUH!)
- It cannot be instantiated (i.e. you cannot do x = MyModule.new)
- It can be mixed in a class to enhance its possibilities
And here is the tricky part : Although a module cannot have instances, it can still have instance methods. How is it possible? Well, you guessed it : those instance methods will become the instance methods of the client (the class).
That is what we call a mixin!
Mixins, quite a funny word don’t you think? No, you’re absolutely right, it is not funny at all. Mixin just means Mixed in, as in : This module has been mixed in my class, let’s play Twister!
When you decide to mix a module in one of your class, the latter automatically gains some new functionalities… for free! In a sense, it’s a little bit like class inheritance, at the exception of these 2 points :
- There is no hierarchy : the class isn’t the child of the module, it just includes it. You just have to see the mixed-in module as a set of additional methods for your class.
- You can include more than one module in a class, thus creating the effect of multiple inheritance… which is something most languages simply don’t allow
A little example to illustrate my first point :
Class inheritance : An aloes (class) IS A plant (class)
Mixins : An aloes (class) HAS some healing properties (module)
If we take for granted that our module “HealingProperties” is general enough in its implementation to fit many purposes, we could very well mix it in any other class that needs healing properties. (a drug, Wolverine, Kenny in South Park, etc). This is how we would do it :
module HealingProperties def heal_wounds #do something that heal wounds end end class Wolverine include HealingProperties def kill_something_with_my_claws! #Arrr! end end logan = Wolverine.new logan.kill_something_with_my_claws! logan.heal_wounds
The Comparable module
The built-in module called Comparable is possibly one of the best example to use to understand the usefulness of modules because it contains general purpose methods that can interest different kind of classes.
Comparable contains the following methods (yes, these are all methods, not operators) :
<, <=, ==, >, >=, between?
What are those methods doing exactly? Well, they compare sortable things together. However, those “things” to compare have to be in the class that includes the module, not in the module itself. How can the module knows what the “client” class is all about? The answer lies in the secret treaty that exists between Comparable and any class that wants to use it. The class has to specify, by implementing the <=> method, the attribute on which the comparison must operate. Note that <=> is a comparison method that is always expected to return +1 if the receiver is higher than the method argument, -1 if the method argument is higher then the receiver and 0 if both the receiver and the method argument are equal. In case you’re confused, if I write x <=> y, x is the receiver and y is the method argument.
class Dog include Comparable attr_accessor :iq def initialize(iq) self.iq = iq end #Honoring the contract with Comparable... def <=>(other) self.iq <=> other.iq end end spike = Dog.new(60) rex = Dog.new(40) retard = Dog.new(30) dumbass = Dog.new(20) dumber = Dog.new(15) bozo = Dog.new(10) puts "Make fun of dumbass" if spike > dumbass puts "Make fun of dumbass even more" if dumbass <= rex puts "Realize in shame that dumbass is not a dog but an African violet" if dumbass < bozo
In this example, I told Comparable that the comparison had to operate on the iq attribute. Doing so, my Dog class gained access to 6 comparison methods without me having to write a single line of code.
A little clarification about <=>
This hasn't much to do with mixins, but it may be confusing at first : Why do we used <=> once more in the implementation of our <=> method? Wouldn't it create some kind of infinite loop? No... because when we do self.iq <=> other.iq, we call the <=> method that resides in the Fixnum class (since iq is a fixnum). The <=> implementation in the Fixnum class already returns -1, 0 or +1 based on which one of the 2 fixnums is the highest. If iq would have been something else that DOESN'T implement the <=> method, we would have been forced to implement the <=> ourselves (e.g. return 0 if this_condition; return -1 if this_other_condition; return 1 if yet_another_condition)
It's modules like Comparable and Enumerable that makes the concept of modules so attracting. Yes, modules can be used as namespaces only, but they really shine when you use them to enhance your classes. They become especially powerful when they are consisted of general purpose methods and when they only require a small piece of information from the class to work properly.