The behavior of the Ruby interpreter makes perfect sense because:
- When a
Child
class extends a Parent
, Ruby sets it up so that the singleton class #<Class:Child>
extends #<Class:Parent>
as well; and
BasicObject.singleton_class
is a subclass of Class
, so BasicObject.singleton_class.singleton_class
will be a subclass of #<Class:Class>
Verifying the equality:
BasicObject.singleton_class.singleton_class.superclass.equal?(Class.singleton_class)
#=> true
This leads to the next question – why does #<Class:BaseObject>
extend Class
in the first place? Following the rule above, since BaseObject
has no superclass – that is, BaseObject.superclass
is nil
– the logical thing would be for its singleton class to not have a superclass either.
The answer is that #<Class:BaseObject>
extending Class
ensures consistency in the inheritance hierarchy when it comes to singleton classes. Take this Ruby object for example:
obj = "a string"
It is a well-established notion that instead of obj
being simply an instance of String
, we can think of it as an (only) instance of its own singleton class, which in turn is a subclass of String
. That is:
obj.class.equal?(obj.singleton_class.superclass)
#=> true
It seems only logical that the same should apply to class instances as well. But it does not, because it contradicts the rule mentioned above, where the superclass of a singleton class of a Child
class is the singleton class of its Parent
class.
class Foo; end
Foo.class
#=> Class
Foo.singleton_class.superclass
#=> #<Class:Object> <-- not equal to Class!
# because:
Foo.superclass
#=> Object
But it is possible to resolve this contradiction by placing Class
at the top of the singleton class inheritance hierarchy:
Foo.singleton_class.superclass
#=> #<Class:Object>
Foo.singleton_class.superclass.superclass
#=> #<Class:BasicObject>
Foo.singleton_class.superclass.superclass.superclass
#=> Class
This way, even though Foo.singleton_class.superclass
is not equal to Foo.class
, by walking up the inheritance chain, it does get there eventually...