I, Object!

I 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 falses 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.quacked 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