I, Object!
23 Aug 2015I am lucky enough to work with wonderfully talented people every single day. This is a guest post by one of my magnificent coworkers, Kristján Pétursson. Thank you for allowing me to share this knowledge with everyone!
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]
Drawing it out
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 ~~~>
to move from objects to their singleton class, and then --->
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.
All together now
Ok, one more iteration. Once again, everything is an Object
- even Module
. You can discard that time traveling 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?
Author: Kristján Pétursson