This post is adapted from my answer to this Stack Overflow question. If you want to start from the beginning of the universe and build out, go read that one. If you prefer to start with something you can touch and work backwards, here we go.
Ruby likes ducks. Which is to say that when we're coding, and we have an
object, we don't particularly care what kind of object it is, so long as it
responds to the messages we send it. It might be a Duck
or a Child
or a
Doctor
, and as long as when we call #quack
we hear a noise, all is well.
That's called Duck Typing, and
Ruby digs it.
So if we have some arbitrary object and we ask it to #quack
, the Ruby
interpreter needs to figure out where the object's #quack
method is.
Nothing's been compiled, and Ruby lets you define methods pretty much any place
or time you like, so #quack
needs to be looked up at runtime. That's called
Dynamic dispatch, and it's how
Ruby handles ducks.
Now for the thing we can touch; let's make a Duck
.
class Duck
def quack
puts "Quack, I say!"
end
end
duck = Duck.new
duck.quack
#=> Quack, I say!
Surely, this is no surprise. You've done this in the past, or quieter things
like it, and you understand just fine that a method called on duck
will be
found in Duck
. But we less frequently do things like
def duck.quack
puts "I'm tired of quacking."
end
duck.quack
#=> I'm tired of quacking.
other_duck = Duck.new
other_duck.quack
#=> Quack, I say!
Hm, so if we can just redefine duck.quack
without messing up other_duck
,
where is the second #quack
method? It turns out every object has a Singleton
Class where it can stash all its personal possessions. Other words for singleton
class include metaclass, eigenclass, and virtual class, but Ruby implements a
method called #singleton_class
, so we'll use that one. You can see #quack
on
duck.singleton_class
, but not on other_duck.singleton_class
:
puts duck.singleton_class.instance_methods(false).inspect
#=> [:quack]
puts other_duck.singleton_class.instance_methods(false).inspect
#=> []
And then you can see the original #quack
on Duck
where we left it:
puts Duck.instance_methods(false).inspect
#=> [:quack]
Now we can start drawing diagrams, which is great, because people like diagrams
almost as much as Ruby likes ducks. When we ask duck
to #quack
, it starts
looking for the method on duck.singleton_class
and then works its way up until
it finds it.
+----------+
| Duck |
| #quack |
+----------+
^
|
+-------------------------------------+
| |
+---------------+ +---------------+
duck ~~~~> | #<Class:Duck> | other_duck ~~~~> | #<Class:Duck> |
| #quack | +---------------+
+---------------+
I'm using ~~~>
squiglies to move from objects to their singleton class, and
then --->
straights to move from classes to their superclass. And indeed,
duck.singleton_class.superclass == Duck
. The #<Class:Duck>
singleton class
is an anonymous class brought into existence just for duck
. other_duck
has
its own singleton class that doesn't have #quack
defined on it, so it
traverses upwards and finds #quack
on Duck
instead.
We can see the whole lookup path with #ancestors
, and can check exactly where
a method is defined with #method
. #ancestors
includes the singleton class as
its first entry because that's the first place we look for a method.
puts duck.singleton_class.ancestors.inspect
#=> [#<Class:#<Duck:0x007fe793031dd0>>, Duck, Object, Kernel, BasicObject]
puts duck.method(:quack)
#=> #<Method: #<Duck:0x007fe793031dd0>.quack>
puts other_duck.method(:quack)
#=> #<Method: Duck#quack>
There are more things in #ancestors
than we've drawn yet, though I bet you saw
them coming. Moving up from Duck
, we get to Object
. Everything in Ruby is an
Object
. No, everything. Yes, everything, even BasicObject
, which is an
ancestor of Object
- just go with that for a second. Maybe pretend there was
time travel involved.
When you class Duck
, there's an implicit class Duck < Object
so your class
can inherit everything Object
has and be a good citizen. The false
s we were
using earlier to look at instance_methods
lets us look only at the methods
that class is defining, rather than everything it has inherited, but in reality:
duck.public_methods
#=> [:quack, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class,
# :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint,
# :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect,
# :methods, :singleton_methods, :protected_methods, :private_methods,
# :public_methods, :instance_variables, :instance_variable_get,
# :instance_variable_set, :instance_variable_defined?,
# :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send,
# :public_send, :respond_to?, :extend, :display, :method, :public_method,
# :singleton_method, :define_singleton_method, :object_id, :to_enum,
# :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec,
# :__send__, :__id__]
And actually, Object
didn't really define any of those itself. It inherited
some from BasicObject
and then included Kernel
to get the rest. When you
include a module, it's inserted into the list immediately after the singleton
class, which explains the end of the #ancestors
list. Our whole object setup
looks like this:
+-------------+
| BasicObject |
| #== |
| #! |
| ... |
+-------------+
^
|
| +----------+
| | Kernel |
| | #nil? |
| | #=== |
| | ... |
| +----------+
| ^
| |
+-----+-----+
|
+----------+
| Object |
+----------+
^
|
|
|
+----------+
| Duck |
| #quack |
+----------+
^
|
|
|
+---------------+
duck ~~~~> | #<Class:Duck> |
| #quack |
+---------------+
This is now everything we can look at to decide how duck
responds to a
message. But what about class methods on Duck
? Well, remember I said
everything in Ruby is an Object
- that means everything in our diagram is like
duck
, and has a singleton class and ancestry chain. In fact, everything here
except for duck
and Kernel
are instances of Class
, so we can build them
out the same way we built duck
. Kernel
is an instance of Module
, and has
the appropriate singleton class with Module
as its superclass, but drawing
that makes the diagram pretty messy.
+--------+
| Module |
+--------+
^
|
+-------+
| Class |
+-------+
^
|
+-------------+ +----------------------+
| BasicObject | ~~~~~~> | #<Class:BasicObject> |
+-------------+ +----------------------+
^ ^
| |
| +----------+ |
| | Kernel | |
| +----------+ |
| ^ |
| | |
+-----+-----+ |
| |
+----------+ +-----------------+
| Object | ~~~~~> | #<Class:Object> |
+----------+ +-----------------+
^ |
| |
| |
| |
+------+ +---------------+
| Duck | ~~~~~~~~> | #<Class:Duck> |
+------+ +---------------+
^
|
|
|
+---------------+
duck ~~~~> | #<Class:Duck> |
+---------------+
When you define a class method on Duck
, you've probably noticed, but maybe
sort of glossed over, that you declare it on self
.
class Duck
def self.in(lake)
# Return all the Ducks in lake
end
end
This is no different than when we def duck.quack
ed to put a new method on
duck
(but not other_duck
). In this context, self
is Duck
, so we're
stashing .in
on Duck.singleton_class
in exactly the same way.
Ok, one more iteration. Once again, everything is an Object
- even Module
.
You can discard that time travelling ancestry paradox from before, and we'll
just add lines for the actual paradox.
+----------+
| |
+--------+ +-----------------+ |
+----------------------------------| Module | ~~~~> | #<Class:Module> | |
| +--------+ +-----------------+ |
| ^ ^ |
| | | |
| +-------+ +----------------+ |
| | Class | ~~~~~~> | #<Class:Class> | |
| +-------+ +----------------+ |
| ^ |
| | |
| +-------------+ +----------------------+ |
| | BasicObject | ~~~~~~> | #<Class:BasicObject> | |
| +-------------+ +----------------------+ |
| ^ ^ |
| | | |
| | +----------+ | |
| | | Kernel | | |
| | +----------+ | |
| | ^ | |
| | | | |
| +-----+-----+ | |
| | | |
| +----------+ +-----------------+ |
+-------> | Object | ~~~~~> | #<Class:Object> | |
+----------+ +-----------------+ |
^ | ^ |
| | | |
| | +----------------------------+
| |
+------+ +---------------+
| Duck | ~~~~~~~~> | #<Class:Duck> |
+------+ +---------------+
^
|
|
|
+---------------+
duck ~~~~> | #<Class:Duck> |
+---------------+
Module
is an Object
, which inherits from BasicObject
, which is a Class
,
which inherits from Module
, which is an Object
, and so on until you hit the
turtles. #ancestors
and other methods that would have trouble with this loop
have special cases in the Ruby source for when they find BasicObject
, and just
pretend that's the end of the line.
If you start from any of these objects and traverse up, right-to-left, depth-first, you can build the ancestry chain showing in what order methods will be found. Everything has a singleton class to handle the things we declare on it, and there's only a little bit of cheating to make the whole thing work. But what did you expect from a system filled with ducks?