Metaclasses in Ruby and Smalltalk

Disclaimer: The following is my understanding (possibly misunderstanding!) of Ruby and Smalltalk metaclasses. I am a Java programmer. I've played a bit with Squeak and Strongtalk. As I said in my earlier posts, I am learning Ruby using the JRuby 0.9.0 implementation. If you are Rubyist or Smalltalker (or both!) and find gross errors here, please let me know [how do we call a Java programmer?]

With class based object-oriented programming languages:

  1. Everything is an object. With few languages, nearly everything is an object;-)
  2. Every object is an instance of some class
  3. classes "hold" methods - while instances of classes share the methods
  4. instances hold instance variables.
  5. A class derives/extends from another class (called superclass) - except for a special "root" class (typically called "Object") whose superclass is null.
  6. method search: When a method is called on an object, the method is looked on it's class, if not found on superclass-of-class and so on (till the superclass is null)
Note: Yes, I've left multiple inheritance!

Point number (1) implies every class is an object too. If so, per point (2) every class has to be an instance of ... well another class [called "metaclass"]. So, what is a metaclass of a metaclass? And how do we end this seamingly "infinite" chain?

Java's answer for this:
  • Every class is represented by an instance of java.lang.Class - you can get that by ClassName.class. Or you can get that using by Class.forName("ClassName")
    
    class Person {
       static {
          System.out.println("I'm " + Person.class);
       }
    }
    
    
  • Given an object "obj", you can get it's class using obj.getClass()
  • There is an instance of java.lang.Class that represents java.lang.Class itself. You can obtain that by Class.class [or Class.forName("java.lang.Class")] - the meta-chain ends here!
  • And obj.getClass().getClass() == Class.class for any object "obj"

Ruby Metaclasses

  • Every class is instance of the class called Class
    
    class Person
    end
    
    # prints true
    puts(Person.instance_of?(Class))
    
    
  • Given an object "obj", you can get it's class using obj.class
  • Class.instance_of?(Class) is true [meta-chain ends at Class]
  • And obj.class.class == Class for any object "obj".

Huh! That looks pretty similar to Java, isn't it? (except for the syntax changes and the metaclass name). No, that is not the complete story. Look at the following Ruby code.


class Person
  # "static" method?
  def Person.greet
    sayHello
  end

  # another "static" method?
  def Person.sayHello
    puts "Person: hello, world"
  end
end

class Employee < Person

  # "Override" "static" method?
  def Employee.sayHello
    puts "Employee: hello, world"
    # calling "super" method?
    super
  end
end

Employee.greet

If you run above program, you get
Employee: hello, world
Person: hello, world

In Java, the static methods don't have "this" object and can't access "super" either. The "static" methods of Java are not really connected to any object in the system -- static "methods" are just like good old global functions except for namespace and accessibility.

Clearly, "static" methods in Ruby are not like static methods of Java. Class methods [as these are known in Ruby and Smalltalk world] are very much like instance methods - can be overriden, can call super method and have "self". "self" inside a class method is - guess what - the class itself (recall that classes are objects!). In fact, "self" used anywhere inside class body (except inside an instance method) refers to the class. For example:


class Person
  puts self
end

So, you can write "class methods" with the syntax as well:

class Person
  # "self" here refers to "Person"
  def self.greet  
    sayHello
  end

  def self.sayHello
    # "self" here refers to "Person"
    puts "hello, world ${self}"
  end

  # instance method
  def whoAraYou
    # "self" here refers to the Person 
    # object on which "func" was called
    puts "I'm ${self}"
  end
end

# call a class method
Person.greet


# call an instance method
p = Person.new
p.whoAreYou


I mentioned class holds instance methods, if so which class would hold class methods of a class? For example, Person "holds" the instance methods such as "whoAreYou" Which class would hold the class methods (such as "greet") of Person class? Is it Person.class? - no, that can not be. Recall that Person.class is Class - which can only "hold" methods common to all classes (like general reflective queries such "superclass", "instance_methods" etc.). The Class class can't hold class methods of the Person class. If Class class had the class methods of, say Person class, then you could call that on any class. For example, you can call "greet" class method of Person class on any other class in the system!

Ruby's answer for this is as follows: in Ruby, every object has an optional singleton class [also known as exclusive class]. Whenever a method is called on an object, first the associated singleton class, if available, is looked up for the method. If not found, only then the "actual" class of the object is searched [and then the usual superclass chain search]. How would you add singleton methods to a specific object - in other words how would you define singleton class for an object? When we defined class methods like Person.greet, you are actually adding a singleton method to Person's singleton class. In fact, you can define singleton class with the following syntax - so that you can add multiple singleton methods in "one shot":


class Person 
  # "self" here refers to "Person"
  # add singleton methods.
  class <<self    
    def greet  
      sayHello
    end

    def sayHello      
      puts "hello, world"
    end
  end
end

For every Ruby class, there is a singleton class associated with it. The singleton class holds the "class methods" of that Ruby class. For Person class, there is Person singleton class. For Employee (which is a subclass Person), there is Employee singleton class and so on. Also, Employee's singleton class is subclass of Person's singleton class [singleton class mirrors regular class hierarchy]

Note that every Ruby object can have an optional singleton class. Yes, that is right -- every object, not just class objects can have a singleton class associated with it.


class Person
end

x = Person.new
y = Person.new

# now, add singleton class to "y" object

class <<y
  def wonder
    puts "I'm wondering!"
  end
end

# calls wonder method in "y"'s singleton class
y.wonder 

# won't work - method_missing error!
x.wonder 

The singleton classes alongwith clone method can be used to write prototype based object-oriented programs (as with Self). i.e., you don't need classes at all. You just create objects (say using Object.new) and you specialize some of your objects by defining singleton classes for those (note that singleton classes are unnamed). Whenever similar behaving objects are needed [you need a class of objects], then clone one or more prototypical objects!

To understand Ruby class, singleton class relationship, the following session with JRuby could help:

Update: - the output of the following script with JRuby 0.9.0 is different from Ruby 1.8.4. It seems that Ruby hides singleton classes as an implementation detail. I've filed a bug with JRuby ancestors() on a singleton class returns result different from Ruby 1.8.4



# add a method called "singleton" 
# to Object class

class Object

  # return singleton class associated
  # with the current object

  def singleton
    # create a singleton class for "self"
    class <<self
      # note that class is expression 
      # in Ruby, we just return "self"
      # Note: "self" here is the singleton
      # class itself

      self
    end
  end
end

class Person
end

class Employee < Person
end

class Manager < Employee
end

puts "Manager's singleton = #{Manager.singleton}"
puts "Employee's singleton = #{Employee.singleton}"
puts "Person's singleton = #{Person.singleton}"

def print_hierarchy(klass)
  puts "#{klass}'s hierarchy" 
  puts "\\t" + klass.ancestors.join("\\n\\t")
end

# print class hierarchy of each class
print_hierarchy(Manager)
print_hierarchy(Employee)
print_hierarchy(Person)

# print class hierarchy of each singleton class
print_hierarchy(Manager.singleton)
print_hierarchy(Employee.singleton)
print_hierarchy(Person.singleton)

From the output of above program, we can see that singleton hierarchy parallels the class hierarchy.

More info on Ruby metaclasses:

Smalltalk Metaclasses

What is the answer of Smalltalk to the class-of-class question? In Smalltalk,

  • Every class is an unique instance of a metaclass. i.e., there are as many metaclasses as there as classes.
  • Metaclass hierarchy paralles the class hierarchy. In the above example, there will be PersonMetaclass, EmployeeMetaclass, ManagerMetaclass. The inheritance is as follows:
      Person
       |
       Employee
          |
          Manager
    
      PersonMetaclass
       |
       EmployeeMetaclass
          | 
          ManagerMetaclass
    
  • All metaclasses (directly or indirectly) inherit from a class called Class
  • Every metaclass is an instance of a class called Metaclass
  • The metaclass of Metaclass is an instance of Metaclass (i.e., meta-chain ends here)

More info on Smalltalk metaclasses:

Smaltalk vs. Ruby metaclasses

Ruby's treatment is similar yet different from that of Smalltalk. Ruby's singleton classes serve like Smalltalk's metaclasses. But, unlike Smalltalk metaclasses which have single instances, we can't create instances of Ruby singleton classes:


jruby>Object.singleton.new
script error: org.jruby.exceptions.RaiseException: can't create instance of virt
ual class
jruby>o = Object.new
#<Object:0x16c9867>
jruby> o.singleton.new
script error: org.jruby.exceptions.RaiseException: can't create instance of virt
ual class

Hence it has been suggested that singleton classes be referred to as "sterile metaclasses" It is important to note that Ruby's singleton classes can be associated with any (ordinary) object (unlike Smalltalk). Because of this, Ruby supports prototype based object orientation.

Comments:

Hmm, are you sure that in Ruby "singleton hierarchy parallels the class hierarchy"? Surely if every singleton class is a direct, anonymous, subclass of Class then the hierarchy for singleton classes will always be <singleton> - <Class> - etc. Running your Ruby example confirms my suspicion, at least with Ruby 1.8.1 under OS X.

If I understand correctly, this contrasts with Smalltalk metaclasses where, for example, "Person class: yields the single instance of the class PersonClass, a subclass of Class, and "Employee class" yields the single instance of the class EmployeeClass, a subclass of PersonClass. (I'm not speaking from a position of authority, I have recently been trying to get my head around the same concepts, but in Smalltalk.)

Posted by guest on September 26, 2006 at 04:45 PM IST #

As I mentioned earlier, I ran all my Ruby scripts with JRuby version 0.9.0. With that implementation, I got singleton's in the ancestor hierarchy. After your comment, I tried Ruby 8.4.2 on Windows -- the singleton classes are not shown in ancestors. It appears that Ruby wants to hide singletons completely. But, singletons do seem to have parallel mirroring hierarchy. For example, when we call
class Person
  def self.greet
    puts "Person's greet"
  end
end

class Employee < Person
  def self.greet
    puts "Employee's greet"
    super
  end
end

Employee.greet

The super call in Employee.greet calls Person.greet -- which cannot be explained if the singleton hierarchy does not parallel the class hierarchy. I'll file a bug in JRuby project on the difference with the return value of ancestors() call.

Posted by A. Sundararajan on September 27, 2006 at 04:09 AM IST #

Post a Comment:
Comments are closed for this entry.
About

sundararajan

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks
Links

No bookmarks in folder

Blogroll

No bookmarks in folder

News

No bookmarks in folder