Why isn't the eigenclass equivalent to self.class, when it looks so similar?

eigenclass, language-features, ruby

Solution

`class << self` is more than just a way of declaring class methods (though it can be used that way). Probably you've seen some usage like:

class Foo
  class << self
    def a
      print "I could also have been defined as def Foo.a."
    end
  end
end

This works, and is equivalent to `def Foo.a`, but the way it works is a little subtle. The secret is that `self`, in that context, refers to the object `Foo`, whose class is a unique, anonymous subclass of `Class`. This subclass is called `Foo`'s eigenclass. So `def a` creates a new method called `a` in `Foo`'s eigenclass, accessible by the normal method call syntax: `Foo.a`.

Now let's look at a different example:

str = "abc"
other_str = "def"

class << str
  def frob
    return self + "d"
  end
end

print str.frob # => "abcd"
print other_str.frob # => raises an exception, 'frob' is not defined on other_str

This example is the same as the last one, though it may be hard to tell at first. `frob` is defined, not on the `String` class, but on the eigenclass of `str`, a unique anonymous subclass of `String`. So `str` has a `frob` method, but instances of `String` in general do not. We could also have overridden methods of String (very useful in certain tricky testing scenarios).

Now we're equipped to understand your original example. Inside `Foo`'s initialize method, `self` refers not to the class `Foo`, but to some particular instance of `Foo`. Its eigenclass is a subclass of `Foo`, but it is not `Foo`; it couldn't be, or else the trick we saw in the second example couldn't work. So to continue your example:

f1 = Foo.new(:weasels)
f2 = Foo.new(:monkeys)

f1.weasels = 4 # Fine
f2.monkeys = 5 # Also ok
print(f1.monkeys) # Doesn't work, f1 doesn't have a 'monkeys' method.

Problem

I've missed the memo somewhere, and I hope you'll explain this to me. Why is the eigenclass of an object different from `self.class`? ``` class Foo def initialize(symbol) eigenclass = class << self self end eigenclass.class_eval do attr_accessor symbol end end end ``` My train of logic that equates the eigenclass with `class.self` is rather simple: `class << self` is a way of declaring class methods, rather than instance methods. It's a shortcut to `def Foo.bar`. So within the reference to the class object, returning `self` should be identical to `self.class`. This is because `class << self` would set `self` to `Foo.class` for definition of class methods/attributes. Am I just confused? Or, is this a sneaky trick of Ruby meta-programming?

Original source