views:

251

answers:

3

In Ruby, we could use super within singleton method to call the corresponding super class's singleton method, like the following code shows.

class Base
  def self.class_method
    puts "Base class method"
  end
end

class Derived < Base
  def self.class_method
    puts "Derived class method"
    super
  end
end

Derived.class_method
# Derived class method
# Base class method

However, I don't seem quite get how that call to super within Derived.class_method could reach Base.class_method. I'd assume that class_method is defined on their metaclass, does that mean their metaclass has parent/child relationship? (I can't quite confirm that by experiments)

Update: I'm asking this question because I remembered seeing somewhere there's some kind of relationship bettwen base and derived class' metaclass (but I can't find it any more). In addition to know how actually super works, I'd also like to confirm whether the two metaclasses are totally separate or not.

+7  A: 

There are four class objects in play here:

<Class>---class---><Class>
Base               #Base
   ^                  ^
   |                  |
   |                  |
 super              super
   |                  |
   |                  |
<Class>            <Class>
Derived---class--->#Derived

Nomenclature:

  • <...> is the class of each object.
  • The name of the class is on the second line.
  • If the name starts with #, it's the eigenclass (aka singleton class).
  • super points to a class's superclass
  • class points to the class's class.

When you call Derived.class_method, Ruby follows the "right one and then up" rule: First go to the object's class, then follow the superclass chain up, stopping when the method is found:

  • The receiver of the "class_method" call is Derived. So follow the chain right to Derived's class object, which is its eigenclass (#Derived).
  • #Derived does not define the method, so Ruby follows the chain up the chain to #Derived's superclass, which is #Base.
  • The method is found there, so Ruby dispatches the message to #Base.class_method

You don't think I knew all this stuff off the top of my head, did you? Here's where my brain got all this meta juju: Metaprogramming Ruby.

Part 2. How to make an "eigenclass" (aka "singleton class") come out of hiding

class Object
  def eigenclass
    class << self
      self
    end
  end
end

This method will return the eigenclass of any object. Now, what about classes? Those are objects, too.

p Derived.eigenclass               # => #<Class:Derived>
p Derived.eigenclass.superclass    # => #<Class:Base>
p Base.eigenclass                  # => #<Class:Base>

Note: Above is from Ruby1.9. When run under Ruby 1.8, you get a surprise:

p Derived.eigenclass.superclass    # => #<Class:Class>
Wayne Conrad
Is there a way to 'see' the relationship between #Base and #Derived? e.g., like the normal `.superclass`.
bryantsai
I added the trick that gets an eigenclass out of hiding. But the diagram is not consistent with what the trick shows :(
Wayne Conrad
well ... #<Class:Derived> is not #<Class:Base>, that's my problem ... Dose the link from #Derived to #Base really exist?
bryantsai
As far as I know, Ruby has exactly one way to do method dispatch: "right then up." For that to be true, Then #Derived's superclass must be #Base. However, the evidence contradicts that.Another great theory ruined by the available evidence.
Wayne Conrad
@Wayne, your theory is correct, it's just that the #class and #super methods never return eigenclasses - eigenclasses are somewhat 'hidden' in Ruby, see here: http://banisterfiend.wordpress.com/2008/10/25/the-secret-life-of-singletons/
banister
Ah, got it. Run under Ruby 1.9, the results are as expected. Under Ruby1.8, our trick for exposing the eigenclass's superclass does not work. Under Ruby 1.9, it does.
Wayne Conrad
make that #superclass (not #super as i stated). And yeah #superclass behaves differently in 1.9 in that it doesn't hide eigenclasses. In 1.8 though #superclass returns the 'eigenclass of the first real superclass'. So given that Class is the first real (i.e non eigenclass or iclass) superclass #superclass will returns Class's eigenclass :)
banister
Even stranger (under 1.8): `Derived.eigenclass.superclass => #<Class:Class>`, but after a call to `Derived.eigenclass.eigenclass`, `Derived.eigenclass.superclass => #<Class:#<Class:Derived>>`. Both calls return `#<Class:Base>` under 1.9.
jleedev
@banister, thanks, that's really quite a research. :)
bryantsai
Agreed. @banister, Thanks for clearing up that mystery.
Wayne Conrad
@jleedev, interesting point, it seems that in Ruby 1.8 the #superclass method (when invoked on an eigenclass) returns (for some bizarre reason) the CLASS of that eigenclass (rather than its superclass): here's the piece of code from the `rb_class_superclass` method in class.c. `if (FL_TEST(klass, FL_SINGLETON)) super = RBASIC(klass)->klass;`
banister
+2  A: 

This diagram explains the relationship: http://banisterfiend.wordpress.com/2008/11/25/a-complete-ruby-class-diagram/

Also, here are some other posts that explain more the intricacies of eigenclasses: http://www.klankboomklang.com/2007/10/05/the-metaclass/

http://www.klankboomklang.com/2007/09/21/the-singleton-class/

And here's a fairly hard-going one that explains more than you'd probably like to know: http://banisterfiend.wordpress.com/2008/10/25/the-secret-life-of-singletons/

banister
I finally got around to reading your hard-going post. Great stuff!
Wayne Conrad
+3  A: 

To clarify and correct what i wrote in the comments regarding the way Ruby hides/exposes eigenclasses, here is the situation:

Ruby 1.8:

(1) The Object#class method always returns the first real (non eigenclass or iclass) superclass of the actual class of an object. e.g

o = Object.new
class << o; end
o.class #=> returns Object, even though the _actual_ class is the eigenclass of o

In other words, the Object#class method will never return an eigenclass, it passes over them and instead returns the first 'real' class it finds in the inheritance hierarchy.

(2) The Class#superclass method has two cases. If the receiver is not an eigenclass then it simply returns the superclass. However, if the receiver is an eigenclass then this method returns the actual (i.e not necessarily real) class of the receiver.

# case where receiver is a normal class (i.e not an eigenclass)
Module.superclass #=> Object (behaves as expected)

# case where receiver is an eigenclass
class << Module; superclass; end #=> returns Class, this is NOT the superclass

From above, Class#superclass behaves as expected in the case of a normal class, but for the eigenclass example it states the superclass of the eigenclass of Module is Class which is not true. From this diagram http://banisterfiend.wordpress.com/2008/10/25/the-secret-life-of-singletons/ we know that the superclass of the eigenclass of Module is actually the eigenclass of Object. I am unsure why Ruby 1.8 has this strange behaviour.

Ruby 1.9:

(1) The Object#class method behaves identically to the 1.8 version.

(2) The Class#superclass method no longer has two cases, it now treats eigenclasses the same way it treats normal classes and returns the actual superclass as expected.

e.g

class << Module; superclass; end #=> #<Class:Object>
banister