In Progress
Unit 1, Lesson 21
In Progress

Singleton Class

Video transcript & code

Just as every Haskell tutorial must eventually explain monads, every Ruby show must eventually get around to explaining singleton classes.

So let's do this.

We'll start with a cow named cletus.

Cows are ruminant animals.

And ruminants are all part of the broader family of ungulates .

Today's topic isn't actually about mammal taxonomy, though. Our real focus is on how objects find their methods. So we're going to write a special method to help us with this task.

We call it where , and it will take an object and a method name as arguments.

Inside, it looks through the object's class ancestors until it finds the nearest ancestor to defined the given method.

With our tools defined, let's put Cletus through his paces.

Cletus can speak, and when he does, he moos.

Cletus get this behavior from the fact that he is a Cow.

When Cletus rests, he chews his cud.

That's because cows are Ruminant mammals.

And when he eats, he grazes.

This is fairly typical of ungulates, a group that also includes horses, deer, and giraffes.

class Ungulate
  def eat
    "graze graze graze"
  end
end

class Ruminant < Ungulate
  def rest
    "chew chew chew"
  end
end

class Cow < Ruminant
  def speak
    "moo"
  end
end
require "./classes"

def where(obj, method)
  obj.class.ancestors.detect{ |klass|
    klass.instance_methods(false).include?(method)
  }
end

cletus = Cow.new
cletus.speak                    # => "moo"
where(cletus, :speak)           # => Cow
cletus.rest                     # => "chew chew chew"
where(cletus, :rest)            # => Ruminant
cletus.eat                      # => "graze graze graze"
where(cletus, :eat)             # => Ungulate

Let's form a hypothesis right here and now about how Ruby objects look up method implementations. We'll conjecture that a method definition always comes from an object's class. And if not from the immediate class, then from one of its ancestors.

But wait a sec. Methods defined on classes aren't the only way to define behavior on a Ruby object.

See, Cletus is no ordinary cow. Cletus is unique among other bovines in that he is a dancing cow.

After defining dance on the cletus object, we can send the dance message just like any other.

But when we ask where this method is defined, our helper method comes up empty.

require "./classes"

cletus = Cow.new

def cletus.dance
  "tappety tappety tap"
end

cletus.dance
# => "tappety tappety tap"

where(cletus, :dance)           # => nil

It seems like our hypothesis wasn't quite accurate. It appears that Ruby first looks for a method definition on the object itself, and then at the object's class.

But here's the thing about the internal design of Ruby: Matz and the other Ruby designers really don't like putting in special cases. If they can find a way to make everything consistently obey a single simple rule, they do. And that's exactly what they did for method lookup.

Our first theory was the correct one: in Ruby, methods are always looked up in an object's class. Always.

But how is this possible, when as we can see right here, objects can have individual methods that aren't part of their class?

The answer lies in the object's Singleton Class.

This special, weirdly-named, anonymous class is unique to each object. In this case, it's unique to the cow we call Cletus. Singleton classes have been called other names, such as "metaclass" or "eigenclass". But the Ruby internal team consistently refers to them as a "singleton classes", and so that's what we're going to call them.

This special class knows that it is special.

If we compare it to the singleton class of another, new Cow object, we can confirm that these two cows do not share a singleton class.

If we look at the instance methods defined in the singleton class for Cletus, we see that this is where Ruby stored our definition of the dance method.

And if we look at the ancestor chain starting from the singleton class instead of from the class, we can now see the whole picture. We see a chain that starts with the object's singleton class, then moves on to its regular class, then upwards through superclasses all the way up through Object and BasicObject.

require "./classes"

cletus = Cow.new

def cletus.dance
  "tappety tappety tap"
end

cletus_class = cletus.singleton_class
# => #<Class:#<Cow:0x0055978f993a78>>

cletus_class.singleton_class? # => true

Cow.new.singleton_class == cletus_class # => false

cletus_class.instance_methods(false)
# => [:dance]

cletus_class.ancestors
# => [#<Class:#<Cow:0x0055978f993a78>>,
#     Cow,
#     Ruminant,
#     Ungulate,
#     Object,
#     PP::ObjectMixin,
#     Kernel,
#     BasicObject]

Which means that in order to get our helper method to show us the truth, all we should need to do is update it to start at the singleton_class instead of at the class.

When we ask the updated helper where the dance method is defined, it finds the singleton class.

require "./classes"

def where(obj, method)
  obj.singleton_class.ancestors.detect{|klass|
    klass.instance_methods(false).include?(method)
  }
end

cletus = Cow.new

def cletus.dance
  "tappety tappety tap"
end

where(cletus, :dance)           # => #<Class:#<Cow:0x0055c1fb9c05c8>>

In Ruby, method lookup follows a simple and consistent rule. An object's method definitions are found via its class inheritance chain. Always. In order for objects to be able to have individual method definitions, they have a singleton class that's unique to them, and that singleton class is the first place Ruby looks for a method definition.

Understand this, and you understand one of Ruby's most difficult concepts. Happy hacking!

Responses