Ruby eigenclass unexpected behaviour
-
21-12-2019 - |
Question
First, let's add a method to retrieve eigenclass "copied from this blog post"
class Object
def eigenclass
class << self
self
end
end
end
Then create a simple class
class A
end
puts A.new.eigenclass.superclass # => A
puts Class.new.eigenclass.superclass # => #<Class:Object>
I was expecting the second puts to output Class
Any clue why this happened?
Solution
From that blogpost, you can construct a similar diagram:
+------------------+ +-------------------+
| Object |- eigenclass ->| Object eigenclass |
+------------------+ +-------------------+
^ ^
| superclass superclass |
+------------------+ +-------------------+
| A |- eigenclass ->| A eigenclass |
+------------------+ +-------------------+
^
| superclass
+-------+ +------------------+
| A.new |- eigenclass ->| A.new.eigenclass |
+-------+ +------------------+
Trying to find the superclass of the eigenclass of an instance of A shows that it points to the A
class.
A.new.eigenclass.superclass # => A
Class.new
returns an instance of a Class object, i.e. a new class. It is a class, just like the A
class.
Class.new.class # => Class
A.class # => Class
A's superclass and Class.new's superclass are both implicitly Object
.
Class.new.superclass # => Object
A.superclass # => Object
Because A's superclass is Object
, A's eigenclass's superclass is Object's eigenclass.
Object.eigenclass # => #<Class:Object>
A.eigenclass.superclass # => #<Class:Object>
A.eigenclass.superclass == Object.eigenclass # => true
Similarly, finding the superclass of the eigenclass of Class.new
yields Object's eigenclass
Class.new.eigenclass.superclass # => #<Class:Object>
The difference between Class.new
and A.new
is that Class.new
is itself a class and so can construct new objects, while A.new
cannot.
Class.new.new # => #<#<Class:0x007f86b50d8f70>:0x007f86b50d8f20>
A.new.new # => NoMethodError: undefined method `new' for #<A:0x007f86b50cbf50>
OTHER TIPS
By puts A.new.eigenclass.superclass
, you are effectively calling #eigenclass
on the instance of class A. I will begin with backstory to explain how eigenclass actually works, and will then proceed to tell what is happening in your code.
BackStory:
EigenClass is a hidden class which contains the singleton methods available for that specific object only.
So for obj = Foo.new
, the class hierarchy actually looks like:
obj --eigenclass--> #> --(superclass)--> A
instead of:
obj --(class)--> A
A hidden class can be produced after you hijacked the self with #eigenclass
.
Now, in Ruby, Class is an object. This also means that #eigenclass
should show the the hidden eigenclass of A
too (where A's sigleton methods are kept).
A --(eigenclass)--> # --(superclass)--> #
Now the reason why it shows # instead of A
is because Ruby organizes the classes, superclasses and eigenclasses in a very beautiful pattern. This can be shown with example instead of quoting it in confusing words:
A.superclass #=> Object
A.eigenclass #=> #<Class: A>
A.eigenclass.superclass #=> #<Class: Object> => Eigenclass of Object
A.eigenclass.superclass == Object.eigenclass #=> true
The superclass of an eigenclass of a class is the eigenclass of the superclass of the original class.
Now, coming to your case: Class.new.eigenclass.superclass
, this is self-explanatory now. Class.new
corresponds to a new anonymous class, say B
, and you are effectively calling eigenclass.superclass
on it. Since the superclass of B
is Object, the superclass of eigenclass of B is the eigenclass of superclass of B
.
Tried my best to explain with examples. Please feel free to clarify it further in comments below; will update the answer accordingly. Complementary(from Pragmatic MR): .
In the figure shown above, D
inherits from C
. So D.eigenclass.superclass
is the eigneclass of (superclass of D)[which is C]. Now C's superclass is Object.. and so is the same logic.
Regards
A class's eigenclass has a whole shadow hierarchy of eigenclasses for the class's ancestors that all go between the eigenclass and Class. This is because classes are expected to inherit their ancestors' class methods. For example, if you did def Numeric.kind_of_number?() true end
, you would expect Fixnum.kind_of_number?
to be true. So you need to have Numeric's eigenclass as an ancestor of Fixnum's eigenclass.