2008-08-28

include Module

Hello. Today about including a module, and about modules in general.

One potential use of a module, as a namespace for a set of functions, you have seen here: Fibonacci numbers - lazy evaluation. Now it's time to show the main use of modules, the one for which they are introduced in Ruby: mixins.

Mixins
Or, more descriptive: (mix-in)s, things that you mix-in. Let's skip the theory for now and go to an example.

Let's say we have a class whose objects we want to make comparable. Let it be
class Person
, and let
p1<p2
if person
p1
is younger than person
p2
. The traditional approach here is like this:
class Person

def initialize(name,age)
# skip validation for simplicity
@name=name
@age=age
end

attr_reader :name,:age

def <(p2)
@age<p2.age
end

end
OK, now we can compare two people like:
t=Person::new("Tom",23.3)
z=Person::new("Zuz",23.2)
t<z #=> false # as expected
But if we try
t>z
or
t==z
or
t>=z
..., it will be a
NoMethodError
, because only the method
<
has been defined. Of course we can define all the 6 comparison methods, but that wouldn't make today's post, would it? Let's do it using a module
Comparable
, already existing in Ruby, and the operator
<=>
.

<=>
The method
<=>
works just like
comapreTo
in Java - it takes an argument and compares
self
with the argument, yielding
-1
,
0
or
1
if
self
is less than, equal, or greater than the argument, respectively. This strange-looking operator is defined for all built-in comparable types in Ruby, try
5<=>7
for instance. Let's define it, bearing in mind that it is already defined for standard types!
class Person
def <=>(p2)
@age<=>p2.age
end
end
That was trivial. Now we could define all the operator like this:
class Person
def <(p2);(self<=>p2)<0;end
def >(p2);(self<=>p2)>0;end
def ==(p2);(self<=>p2)==0;end
def >=(p2);(self<=>p2)>=0;end
def <=(p2);(self<=>p2)<=0;end
end
Remember one thing: In Ruby, if something looks inefficient, it probably is. So, the above code looks inefficient. First, because it has very low entropy, meaning it repeats the same thing over and over again, and second, because if we define another class which we also want to be comparable, we'll have to copy the 5 lines without any difference duplicating the code and lowering the entropy even more.

One more word - we don't define
!=
because it is automagically defined as the opposite to
==
and even cannot be redefined.

module
Let's do it like it should be done! Let's define the five comparison methods in a module, and let's mix the module into our class like this:
module Comparable
def <(p2);(self<=>p2)<0;end
def >(p2);(self<=>p2)>0;end
def ==(p2);(self<=>p2)==0;end
def >=(p2);(self<=>p2)>=0;end
def <=(p2);(self<=>p2)<=0;end
end

class Person
include Comparable
end

class OtherComparableClass
include Comparable
end
Isn't that better? Now it's going to turn out even more better when I tell you the module
Comparable
is already defined in Ruby, with the five functions just like we defined them here, so when you want to make a class comparable, you just
include Comparable
and
def <=>(other)
, and all works! The module has also a bonus:
between?(min,max)
, working like expected (both ends inclusive).

Now note one thing: the module uses the method
<=>
even though it is not define in it, nor in its ancestors, nor anywhere. But module is a trusty animal: it trusts you that you won't include it unless you define all the missing methods it uses!

The biggest thing in the world
Let's play for a moment with
Comparable
. Let's define an object that claims to be the biggest object in the world.
biggest=Object::new

class << biggest
include Comparable
def <=>(other)
other.equal?(self) ? 0 : 1
end
end

biggest>5 #=> true
biggest<=1000000 #=> false
biggest>["X"] #=> true
biggest>biggest #=> false
What we did: we defined the object, then we sort of declared sort of class that our
biggest
is sort of instance (in fact it's the object's eigenclass, but let's leave it for another post). Just understand that declaring the methods like we do it here is exactly like declaring them in the object's regular class, only they are accessible only for our
bigger
, and not for all the ``Object`` instances. It's defining methods just for one object (as there is only one biggest object, of course!).

The
other.equal?(self) ? 0 : 1
part might need an explanation. If we just returned
1
, then the object would be greater than all objects including itself, so
biggest>biggest
would yield
true
, and
biggest<biggest
would return
false
. We want the object to know that it is as big as it is, so when the object is compared with itself, we want it to know they are equal. That's why we compare it with itself. Now, why we use
equal?
and not
==
? Well, the method
==
defined inside
Comparable
calls
<=>
which in turn calls
==
and so on until
SystemStackError
. But the function
equal?
works in another way: it checks if the two object are the same
instances
:
a="abc"
a=="abc" #=> true
a.equal?("abc") #=> false # other instance of String
a.equal?(a) #=> true
a.equal?(a.dup) #=> false
So that's the method we're looking for - it will return
true
only if we compare
biggest
with
biggest
itself.

One word of a summary: if you define a method within a class, you have to instantiate the class to make the method really accessible to the world. But if you define a method within a module, you first have to include the module in a class, and then to instantiate the class, to be able to call the method.

Kernel and puts
What's this
Kernel
? It's a module that is included in the class
Object
, and thus in all the classes you ever define, as they all are descendants of the class
Object
. Now there comes the explanation how come you can write
puts
and it works.

If you open irb, or take an empty .rb file, you are inside a class. Check it:
irb(main):099:0> self
=> main
irb(main):100:0> self.class
=> Object
So, were inside some
Object
instance. So what happens if we write
puts
? We call our object's private method
puts
. We can call it even though it is private because we are inside the object (but if you try
self.puts
you'll see it is private; by the way, it is not like in Java here -
self
is just as any other object so you cannot call private methods on
self
, you can only do it without prefixing them with
self
). But the truth is that the method is not defined in the object - it is defined inside the
Kernel
module (as a private function), and in this way it got to our
main
object. And to any other object, too:
class K
def kk
puts "in K"
end
end
Now, the call to
puts
that you do when writing
k=K::new; k.kk
is a call to
k
's private method
puts
, which is there also because of mixing in
Kernel
into the class
K
(into the
K
's ancestor:
Object
, precisely speaking). So, if we had a class
StupidClass
, and we were tired of all the noise this class' instances do by
:puts
ing stupid things, we can mute all class' instances:
class StupidClass
def puts(*args)
# ignore
end
end
Other classes, including our
main
, will still be able to
puts
messages.

But if we don't want to mute the messages completely, but just to make them less noisy, we can do this:
class StupidClass
def puts(*args)
Kernel.puts(*args.map{|a| a.to_s.downcase})
end
end

stupid_object.puts "AbC",5,:XX
# Output:
abc
xx
Of course we have to call the
Kernel
's static method
puts
and not just write
puts
, because it would make a self-reference. Also note that the static method
Kernel.puts
is not the one that is included in the class
Object
.
Kernel
has two
puts
methods:
irb(main):009:0> Kernel.private_instance_methods.grep /puts/
=> ["puts"] # instance method, called by Object::new.puts
irb(main):010:0> Kernel.singleton_methods.grep /puts/
=> ["puts"] # static method, called by Kernel.puts
The two methods are independent - none of them calls the other.

No comments: