Blog
0 pixels scrolled
  • Home
  • Work
  • Services
  • About
  • Blog
  • Contact
Where The Wild Methods Are
Jared Norman on July 24, 2018
Something about methods
Where The Wild Methods Are
Jared Norman on July 24, 2018
Posted in Ruby
← Back to the blog

There’s a certain elegance to Ruby’s method lookup when you examine how it really works. Despite the variety of ways we have to define methods there’s really only one kind of method. Think of all the methods you define in your applications. Ignoring metaprogramming (we’ll get to that), they probably look like one of these:

class SomeClass
  def self.class_method
    # Either you're defining a class method like this.
  end

  class << self
    def other_class_method
      # Or you're defining a class method like this.
    end
  end

  def instance_method
    # Or you're defining and instance method like this.
  end
end

Let’s ignore the first case for now; we’ll come back to it. The second two cases have the same basic appearance. The difference is only in the context in which they are called.

As I mentioned in my article about Rake, a given context in Ruby has two important attributes: the value of self and the current class. The current class dictates where the methods we define will actually end up.

A Minimal Example

Let’s define a method on a class and look where it ended up. Let’s implement a Potato class.

class Potato
  def grate
  end
end

Ruby has a whole bunch of tools for introspecting classes, but for our purposes we only need to use #public_instance_methods, which returns all the instance methods of the receiving class. If we pass in false, it will only return the methods defined on the class itself and not the ones from its ancestors.

Potato.public_instance_methods(false)
#=> [:grate]

We defined a class with one public instance method, and this shows us that the class does in fact have that one public instance method on it. Perfect.

What about when we define class methods using the class << self idiom? The syntax for defining the method is exactly the same, so it follows that we must be defining a public instance method on some class, but which one?

class Potato
  class << self
    def russet
    end
  end
end

Yehuda Katz did a great job of explaining metaclasses over here, but the short version is that inside a class << SomeClass block both the current class and self are the “singleton class” (also called the “metaclass”) of SomeClass. For our snippet above, that means with must have defined an instance method on the singleton class of Potato. Let’s verify:

Potato.singleton_class.public_instance_methods(false)
#=> [:russet]

Potato.russet is definitely the class method we intended to define, but it’s also an instance method on this weird “singleton class”. It turns out Ruby’s method lookup works exactly the same whether you’re calling a method on an object, a string, nil, or even modules and classes.

The Singleton Class

It turns out that every object in Ruby can have a singleton class. Objects don’t need singleton classes, but if when there comes a situation where an object needs one it gets created magically. For the purposes of most Ruby programmers they aren’t any different from any other anonymous class.

The previous paragraph contains useful lies. I promise to reveal the truth behind my lies before the end of this article.

Here’s what you get if you look at some singleton classes in your console:

Object.singleton_class
=> #<Class:Object>

"foo".singleton_class
=> #<Class:#<String:0x00007fc91c911d28>>

(0..3).singleton_class
=> #<Class:#<Range:0x00007fc8dc077298>>

Class.singleton_class
=> #<Class:Class>

Class.singleton_class.singleton_class
=> #<Class:#<Class:Class>>

You’ll notice Ruby has a special notation for singleton classes have a consistent notation; #<Class:X> means “the singleton class of whatever “X” is. This comes in handy when poking around in the console.

Look Up ☝️

The purpose of the singleton class isn’t immediately obvious or at least it wasn’t to me, but examining them within the context of Ruby’s method lookup algorithm might shed some light. Let’s look at an example:

potato.grate

Here, we’re calling the grate method on an instance of Potato. Maybe we’re making grated hashbrowns or some latkes. When we do this in Ruby, the interpreter needs to find the definition of the grate method so that it can execute it. Essentially, Ruby looks at the singleton class of the receiver (“the receiver” is the object we’re calling the method, potato in this case) and iterates through its ancestors until it finds the instance method it is looking for.

potato.singleton_class.ancestors
#=> [#<Class:#<Potato:0x00007fcdd28365d8>>, Potato, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]

potato.singleton_class.instance_methods(false)
#=> []

Potato.instance_methods(false)
#=> [:grate]

The method wasn’t defined on the singleton class, but it was defined on the next element in the list, the Potato class itself.

If you’re familiar with how the Ruby inheritance model is normally taught, you might be confused by my explanation here. You may have been taught that when calling a method on an object, Ruby looks at any prepended modules of it’s class, then the class itself, then included modules on the class, and then does the same for the other classes in the inheritance chain. This is also essentially correct, especially if the explanation also mentioned the singleton class.

My explanation, that Ruby iterates through the ancestors of the singleton class looking for the method, demonstrates the elegance (as I see it) of Ruby’s lookup algorithm, but hides its complexity. My explanation is only helpful to you if you know what will be in the ancestors of an object’s singleton class, as well as what order those entries will be in.

Singleton Ancestry

Alright, so if Ruby is able to simply iterate through the ancestors of the receiver’s singleton class to find the method, then the ancestors of the singleton class needs to contain:

  1. The singleton class of the receiver
  2. Any modules prepended to the class of the receiver
  3. The class of the receiver
  4. Any modules included in the class of the receiver

From there Ruby continues on repeating steps for 2-3 for the super class of the receiver’s class, and that class’s superclass, and so on. This sounds complicated and that’s because it is, but here’s an example that might help you wrap your head around this.

module One
end

module Two
end

module Three
  include Two
end

module Four
  prepend One
end

class A
  include Three
end

class B < A
  include Four
end

# I've removed Object's ancestors here because almost every
# inheritance chain includes Object and its ancestors.
B.new.singleton_class.ancestors - Object.ancestors
#=> [#<Class:#<B:0x00007f873c07d698>>, B, One, Four, A, Three, Two]

In this example, class B inherits from A and includes the module Four, which has module One prepended to it. Class A inherits from Object and includes the module Three, which has module Two included in it. It’s a lot to take in, so let’s walk through it.

The ancestors of the singleton class of an instance of B should contain before anything else the singleton class of the instance. We can see it there, in the notation we saw previously. Next we need to look at the instance’s inheritance chain. We’ll continue pretending Object doesn’t exist, so for our purposes the inheritance chain contains B and then A.

B has the Four module included in it, which has the One module prepended to it, so we expect to see B, then One, then Four, and we do. Next we look to A and its included/prepended modules. A includes Three which includes Two. That means we should see A, then Three, then Two in the list. Sure enough, that’s what’s in there.

If you’re totally lost at this point, I can’t blame you. You might be well served to play around with inheritance in an IRB session.

Work ServicesAboutBlogCareersContact


Privacy policyTerms and conditions© 2018 Super Good Software Inc. All rights reserved.