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
OK, now we can compare two people like:
But if we try
<=>
The method
That was trivial. Now we could define all the operator like this:
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
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:
Isn't that better? Now it's going to turn out even more better when I tell you the module
Now note one thing: the module uses the method
The biggest thing in the world
Let's play for a moment with
What we did: we defined the object, then we sort of declared sort of class that our
The
So that's the method we're looking for - it will return
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
If you open irb, or take an empty .rb file, you are inside a class. Check it:
So, were inside some
Now, the call to
Other classes, including our
But if we don't want to mute the messages completely, but just to make them less noisy, we can do this:
Of course we have to call the
The two methods are independent - none of them calls the other.
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<p2if 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
t=Person::new("Tom",23.3)
z=Person::new("Zuz",23.2)
t<z #=> false # as expected
t>zor
t==zor
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<=>7for 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
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
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
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 Comparableand
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
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 : 1part might need an explanation. If we just returned
1
, then the object would be greater than all objects including itself, so biggest>biggestwould yield
true
, and biggest<biggestwould 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
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
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.putsyou'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
puts
that you do when writing k=K::new; k.kkis 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
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
Kernel
's static method puts
and not just write puts
, because it would make a self-reference. Also note that the static method Kernel.putsis 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
No comments:
Post a Comment