some call it singleton class, some metaclass or even eigenclass.
in this blog entry i will give some explanation about the concept of singleton class (which i will call it during this post) and some (more or less useful) examples of its application.
you’re one-of-a-kind
as you may know, ruby lets you enhance single objects by adding a new method to just one instance of an arbitrary class, leveraging this specific object to a real ‘singleton’ (in another sense, in contrast to the well known singleton pattern). since all methods are defined and housed in the objects class’, such new added methods also have to be ‘append’ to and hence reside in the objects class.
lets say we want to add a method palindrome? to only one specific instance of a string:
aString = "never odd or even" class << aString def palindrome? self.gsub(/\s/, '') == self.reverse.gsub(/\s/, '') end end aString.palindrome? # => true
only this single object of String now offers this new method palindrome? (a call to any other instance of String would result in a NoMethodError)
now one could ask, where this new method is going to reside. it can’t be send to the class String. if so, then all String objects would offer this method. that’s where our singleton class comes into the game …
especially for you
whenever a method is added to a single object in the above indicated way, ruby will create an extra class especially for that single object! and exactly this one is – you guessed right – our so called singleton class!
this singleton class is a virtual one, since it never appears as a fully-flegded member in the list of classes (remember that all classes are objects, which are accessible by their names as constants). ruby will insert this singleton class as the direct (again – virtual) class of the object. the former direct class – in the examples case the class String – will slide one level up, so the singleton class ‘sits’ between the object and the original class of the object.
now, whenever a method call (message) is send to the object, ruby will (as always) look at the direct class of the object, wheter the method is defined within that class. if so, ruby is happy and executes that method. if not, ruby will go to the ancestor (super) class (which is now the original classof the object) in order to track the message call and so on.
everywhere i go
we now saw the mechanism of how ruby is able to add methods to single instances. going one step further we could ask ourself if it’s possible to use this mechanism inside a class. we could for example offer a convenient instance method palindromize() inside the class of String in order to add the palindrome?() method to arbitrary instances of String in a quick and easy way. and of course, it’s possible:
class String def palindromize class << self def palindrome? self.gsub(/\s/, '') == self.reverse.gsub(/\s/, '') end end end end s = "otto" s.respond_to?( :palindrome? ) # => false s.palindromize s.respond_to?( :palindrome? ) # => true
note, that we now use self to ‘append’ the new method. since we are inside an instance method, self refers to the object itself (on which the instance method is called) and hence the effect is the same as if you would append a new method to a single object explicitly outside a class (like in the first example)! we refer to a specific object instance in both cases.
catch me if you can
one interesting point is the scope of self inside of class << self … end. as you may have suspected, we are in scope of that ominous singleton class, and you’re right …
class String def palindromize class << self puts "#{self}" # => #<Class:#<String:0x2b418e0>> ... end end end
as you can see, puts prints the ‘signature’ of the singleton class – in the example’s case the singleton class for the String object #<String:0x2b418e0> on which the method was called.
this brings us to the point where we could dare a look at a often mentioned method (mostly even called singleton_class() or even metaclass() or eigenclass()) and understand easily what’s going on there:
class String # could be an arbitrary class def singleton_class class << self; self; end end end
this instance(!) method is a convenient way to access the singleton class of an object on which the method gets called. as you can see (and nothing new to us), we refer to the singleton class again (class << self), but this time not to define a new method inside the scope of that singleton class but to return the singleton class of that object itself (with self being the last evaluated expression of that method).
i count on you
catching that singleton class as an object gives us completely new options towards a way of metaprogramming or at least a kind of generic programming. for example, it’s easy to add this method inside a module together with some generic functions that operate on the singleton class of an object:
module Appendable def singleton_class class << self; self; end end def append_method( method_name, &prc ) singleton_class.class_eval do define_method( method_name, prc ) end end end
so what’s going on here?
first of all we have the above mentioned method aside that allows us to refer to the singleton class of an object. we access the singleton class inside the method append_method(), which takes a method name and a proc. both arguments will constitute a new method that will be appended to a single object via its singleton class.
since we define such a method in a completely generic way in that the name of the method to append is dynamic, we use class_eval (so we can define parts of that method in a generic way and let ruby evaluate the whole expression).
now we’re able to append methods very dynamic to objects of an arbitrary class which includes that module:
class Prototype include Appendable end p = Prototype.new p.append_method( :say_hello ){|n| puts "hello #{n}" } p.say_hello( 'Dave' ) # => hello Dave
using include (in contrast to extend) includes the modul’s methods as instance methods. that’s what we need, since we want to catch an objects singleton class. having done so enables us to append arbitrary methods to our Prototype class in a dynamic way. calling the modul’s append_method(), passing a method name and a block will lead to the dynamic added method (remember, that the last argument &prc inside the argument list of append_method() will implicit call to_proc which will transfer the given block into a proc object).
no magic at all
as you’ve seen, there’s no magic about this often mentioned singleton class, metaclass or eigenclass. it’s one of rubys mechanisms which allows for dynamic programming and high flexibility.
once you have seen and understand the semantics behind these concept, it should be easy to build your own functionality on top of this instrument …
November 22, 2007 at 10:23 pm
Nice one mate – I also found Why’s article quite helpful too in understanding metaclasses: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
Cheers,
Marcus
November 22, 2007 at 10:24 pm
hi. thanks for the clear explanation. i was searching for the explanation of (class << self; self; end) and you did it very well.
November 22, 2007 at 10:24 pm
thanks for your feedback.
i once had the same question – this blog entry is the result ;o)
July 22, 2008 at 12:46 am
excellent explanation of singleton classes. Thanks for contributing to the Ruby community
April 2, 2009 at 6:39 pm
Машины – вот счастье!))
April 21, 2009 at 12:34 am
Слышал продолжение телепузиков снимают
January 10, 2010 at 11:19 am
[…] and even a singleton. Used via class << instance_name … end, it indicates that Ruby creates an eigenclass around that instance and endows that class with whatever code comprising the … in the snippet above. One could think […]
November 28, 2010 at 11:36 am
hi I was luck to look for your blog in digg
your subject is superb
I learn a lot in your theme really thank your very much
btw the theme of you website is really impressive
where can find it
November 29, 2010 at 1:24 pm
how are you I was fortunate to discover your theme in digg
your post is excellent
I learn a lot in your Topics really thanks very much
btw the theme of you site is really impressive
where can find it