Nested singleton class method lookup
Asked Answered
A

2

11

First of all, I understand that this question has no application in real world, I'm just curious.

Imagine we have a class with a singleton method:

class Foo
    def self.bar
    end
end

If we call Foo.bar, it will first search for a method in a singleton class of each ancestor of Foo, and then will look in a class referenced by .class method and its ancestors. We can confirm that with Foo.singleton_class.ancestors, which returns:

[#<Class:Foo>, #<Class:Object>, #<Class:BasicObject>,
 Class, Module, Object, Kernel, BasicObject]

But what happens if we have a nested singleton class, like:

class Foo
  class << self
    class << self
      def bar
      end
    end
  end
end

If we call Foo.singleton_class.singleton_class.ancestors, it returns:

[#<Class:#<Class:Foo>>, #<Class:#<Class:Object>>,
 #<Class:#<Class:BasicObject>>, #<Class:Class>, #<Class:Module>,
 #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

I don't understand how this hierarchy is organized.

Adder answered 2/8, 2015 at 19:10 Comment(3)
You're assuming it's organised at all. Are you sure it is / should be organized?Sort
Perhaps you could clarify your question? Are you asking where the bar method in your second example is defined?Laevo
The bar method is defined. I'm asking how the inheritance hierarchy in the second example is organized.Adder
P
19

Much of this explanation is based on How Ruby Method Dispatch Works by James Coglan, a little of the Ruby Hacking Guide, and just a smidge of source.

To begin with a summary, the ancestry looks like this:

                                                           +----------------+
                                                           |                |
+--------------------------- Module ~~~~~~~~~~~~~~> #<Class:Module>         |
|                              ^                           ^                |
|                              |                           |                |
|                            Class ~~~~~~~~~~~~~~~> #<Class:Class>          |
|                              ^                           ^                |
|                              |                           |                |
| BasicObject ~~~~~> #<Class:BasicObject> ~~> #<Class:#<Class:BasicObject>> |
|     ^                        ^                           ^                |
|     |        Kernel          |                           |                |
|     |          ^             |                           |                |
|     |          |             |   +-----------------------|----------------+
|     +-----+----+             |   |                       |
|           |                  |   v                       |
+-------> Object ~~~~~~> #<Class:Object> ~~~~~~~~> #<Class:#<Class:Object>>
            ^                  ^                           ^
            |                  |                           |
           Foo ~~~~~~~~> #<Class:Foo> ~~~~~~~~~~> #<Class:#<Class:Foo>>

---> Parent
~~~> Singleton class

Let's start from the beginning and build out. BasicObject is the root of everything - if you check BasicObject.superclass, you get nil. BasicObject is also an instance of Class. Yes, that gets circular, and there's a special case in the code to deal with it. When A is an instance of B, A.singleton_class is a child of B, so we get this:

                           Class
                             ^
                             |
BasicObject ~~~~~> #<Class:BasicObject>

Object inherits from BasicObject. When A inherits from B, A is a child of B and A.singleton_class is a child of B.singleton_class. Object also includes Kernel. When A includes B, B is inserted as the first ancestor of A (after A itself, but before A.superclass).

                           Class
                             ^
                             |
BasicObject ~~~~~> #<Class:BasicObject
    ^                        ^
    |        Kernel          |
    |          ^             |
    |          |             |
    +-----+----+             |
          |                  |
        Object ~~~~~~> #<Class:Object>

Kernel is an instance of Module. It's the only instance of Module we'll see, and its singleton class doesn't appear in any ancestry chains, so I won't draw beyond it.

Now we get down to Foo, which inherits from Object (though you don't need to write < Object). We can already figure out what Foo and its singleton class are children of.

                           Class
                             ^
                             |
BasicObject ~~~~~> #<Class:BasicObject>
    ^                        ^
    |        Kernel          |
    |          ^             |
    |          |             |
    +-----+----+             |
          |                  |
        Object ~~~~~~> #<Class:Object>
          ^                  ^
          |                  |
         Foo ~~~~~~~~> #<Class:Foo>

Now Class inherits from Module, and Module inherits from Object, so add Module and the appropriate singleton classes. Because Module < Object and Object < BasicObject and BasicObject.instance_of?(Class), this is where the drawing gets a little funky. Remember you just stop traversing upwards whenever you hit BasicObject.

                                                           +----------------+
                                                           |                |
+--------------------------- Module ~~~~~~~~~~~~~~> #<Class:Module>         |
|                              ^                           ^                |
|                              |                           |                |
|                            Class ~~~~~~~~~~~~~~~> #<Class:Class>          |
|                              ^                                            |
|                              |                                            |
| BasicObject ~~~~~> #<Class:BasicObject>                                   |
|     ^                        ^                                            |
|     |        Kernel          |                                            |
|     |          ^             |                                            |
|     |          |             |   +----------------------------------------+
|     +-----+----+             |   |
|           |                  |   v
+-------> Object ~~~~~~> #<Class:Object>
            ^                  ^
            |                  |
           Foo ~~~~~~~~> #<Class:Foo>

Last step. Every instance of Class has a singleton_class (though it won't be instantiated until it's needed, or else you'd need more RAM). All of our singleton classes are instances of Class, so they have singleton classes. Watch out for this sentence: A class's singleton class's parent is the class's parent's singleton class. I don't know if there's a succinct way to state that as far as type systems go, and the Ruby source pretty much says it's just doing it for consistency in any case. So, when you ask for Foo.singleton_class.singleton_class, the language happily obliges you and propagates the necessary parents upward, leading finally to:

                                                           +----------------+
                                                           |                |
+--------------------------- Module ~~~~~~~~~~~~~~> #<Class:Module>         |
|                              ^                           ^                |
|                              |                           |                |
|                            Class ~~~~~~~~~~~~~~~> #<Class:Class>          |
|                              ^                           ^                |
|                              |                           |                |
| BasicObject ~~~~~> #<Class:BasicObject> ~~> #<Class:#<Class:BasicObject>> |
|     ^                        ^                           ^                |
|     |        Kernel          |                           |                |
|     |          ^             |                           |                |
|     |          |             |   +-----------------------|----------------+
|     +-----+----+             |   |                       |
|           |                  |   v                       |
+-------> Object ~~~~~~> #<Class:Object> ~~~~~~~~> #<Class:#<Class:Object>>
            ^                  ^                           ^
            |                  |                           |
           Foo ~~~~~~~~> #<Class:Foo> ~~~~~~~~~~> #<Class:#<Class:Foo>>

If you start from any node in this graph and traverse depth-first, right to left (and stop at BasicObject, you get the node's ancestor chain, just like we wanted. And, we've built it up from some basic axioms, so we might just be able to trust it. Lacking trust, there are a couple interesting ways to verify the structure further.

Try looking at node.singleton_class.ancestors - node.ancestors for any node in the graph. This gives us the ancestors of the singleton class that are not the ancestors of the node itself, which eliminates some of the confusing redundancy in the list.

> Foo.singleton_class.singleton_class.ancestors - Foo.singleton_class.ancestors
 => [#<Class:#<Class:Foo>>, #<Class:#<Class:Object>>, #<Class:#<Class:BasicObject>>,
     #<Class:Class>, #<Class:Module>]

You can also verify any one parent with node.superclass.

> Foo.singleton_class.singleton_class.superclass
 => #<Class:#<Class:Object>>

And you can even verify that the object identity is all consistent, so there aren't anonymous classes popping up all over the place with no particular relationship to each other.

> def ancestor_ids(ancestors)
>   ancestors.map(&:object_id).zip(ancestors).map{|pair| pair.join("\t")}
> end

> puts ancestor_ids(Foo.ancestors)
70165241815140  Foo
70165216040500  Object
70165216040340  Kernel
70165216040540  BasicObject

> puts ancestor_ids(Foo.singleton_class.ancestors)
70165241815120  #<Class:Foo>
70165216039400  #<Class:Object>
70165216039380  #<Class:BasicObject>
70165216040420  Class
70165216040460  Module
70165216040500  Object # Same as Foo from here down
70165216040340  Kernel
70165216040540  BasicObject

> puts ancestor_ids(Foo.singleton_class.singleton_class.ancestors)
70165241980080  #<Class:#<Class:Foo>>
70165215986060  #<Class:#<Class:Object>>
70165215986040  #<Class:#<Class:BasicObject>>
70165216039440  #<Class:Class>
70165216039420  #<Class:Module>
70165216039400  #<Class:Object> # Same as Foo.singleton_class from here down
70165216039380  #<Class:BasicObject>
70165216040420  Class
70165216040460  Module
70165216040500  Object
70165216040340  Kernel
70165216040540  BasicObject

And that, in a nutshell, is how you snipe a nerd.

Pollinosis answered 7/8, 2015 at 5:18 Comment(0)
L
3

#<Class:Foo> is the eigen/anonymous class of a given class Foo. If this eigen/anonymous class also has been extended, another eigen class would have got created - which thus gets represented as #<Class:#<Class:Foo>>

Parent of an eigen class is eigen class of Object class, whose parent is eigen class of BasicObject. Similarly, parent of an eigen class of another eigen class is the eigen class of the eigen class of the Object class, and so on.

Below code coupled with this explanation gives more insights

p Foo.class
p Foo.class.ancestors
puts "-----------------"
p Foo.singleton_class
p Foo.singleton_class.ancestors
puts "-----------------"
p Foo.singleton_class.singleton_class
p Foo.singleton_class.singleton_class.ancestors

which outputs

Class
[Class, Module, Object, Kernel, BasicObject]
-----------------
#<Class:Foo>
[#<Class:Foo>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
-----------------
#<Class:#<Class:Foo>>
[#<Class:#<Class:Foo>>, #<Class:#<Class:Object>>, #<Class:#<Class:BasicObject>>, #<Class:Class>, #<Class:Module>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Eigen class hierarchy as seen above will repeat to any number of levels. For example:

p Foo.singleton_class.singleton_class.singleton_class.singleton_class.singleton_class
puts "-----------------"
p Foo.singleton_class.singleton_class.singleton_class.singleton_class.singleton_class.ancestors

Above code outputs

#<Class:#<Class:#<Class:#<Class:#<Class:Foo>>>>>
-----------------
[#<Class:#<Class:#<Class:#<Class:#<Class:Foo>>>>>, #<Class:#<Class:#<Class:#<Class:#<Class:Object>>>>>, #<Class:#<Class:#<Class:#<Class:#<Class:BasicObject>>>>>, #<Class:#<Class:#<Class:#<Class:Class>>>>, #<Class:#<Class:#<Class:#<Class:Module>>>>, #<Class:#<Class:#<Class:#<Class:Object>>>>, #<Class:#<Class:#<Class:#<Class:BasicObject>>>>, #<Class:#<Class:#<Class:Class>>>, #<Class:#<Class:#<Class:Module>>>, #<Class:#<Class:#<Class:Object>>>, #<Class:#<Class:#<Class:BasicObject>>>, #<Class:#<Class:Class>>, #<Class:#<Class:Module>>, #<Class:#<Class:Object>>, #<Class:#<Class:BasicObject>>, #<Class:Class>, #<Class:Module>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
Loredo answered 6/8, 2015 at 18:49 Comment(1)
Nice work. I was going to answer this, but I wanted to get my terminology straight before I started. You beat me to it. This was why I was asking the questions that I did here #31776076 Answering where the method is defined requires explaining the inheritance hierarchy.Laevo

© 2022 - 2024 — McMap. All rights reserved.