Hacker News new | ask | show | jobs
by inopinatus 2040 days ago
The language mostly refers to them as "singleton classes", because logically speaking there is exactly one of them for every object.

Every Ruby object is an instance of its singleton class. Even when it appears to be an instance of, say, "File" or "Hash", their true individual identity is their singleton. Hence with:

    class Foo; end
    foo = Foo.new
then

    foo.singleton_class            #=> #<Class:#<Foo:0x00007ff6cc195ee0>>
    foo.singleton_class < Foo      #=> true
    foo.is_a?(foo.singleton_class) #=> true
    foo.singleton_class.ancestors  #=> [#<Class:#<Foo:0x00007ff6cc195ee0>>, Foo, Object, Kernel, BasicObject]
Most significantly, this class is where any per-object method is actually contained, hence:

    class Foo
      def self.hello
        42
      end
    end

    Foo.hello #=> 42
    Foo.singleton_class.instance_methods(false) #=> [:hello]
There are three things worth observing that, once fully absorbed, helped me understand all this more instinctively:

1. All Ruby methods are the instance methods of a class.

2. What we call class methods, such as Foo.hello above, are technically instance methods of the singleton class of a Class object. But that's something of a mouthful, so we say class method instead.

3. Extending an object with a module is, by definition, including that module in the ancestors of its singleton.

2 comments

1. As instance methods always defined in some class:

    foo = Object.new
    def foo.hello
      12
    end
    
    foo.method(:hello).owner == foo.singleton_class
and class definition is just a syntactic sugar:

    Foo = Class.new
    def Foo.hello
      42
    end

    Foo.method(:hello).owner == Foo.singleton_class
2. Class method is just a method of objects class

    f = Foo.new
    f.class.hello
It's dangerous to assume that definition by keyword is syntactic sugar, because there are crucial lexical differences that will bite the novice metaprogrammer on the backside. For example, writing

    class Foo; end
differs from

    Foo = Class.new
since the former will open an existing class, whilst the latter will overwrite the constant with a new class, and then the cref (roughly speaking, constant search path) is different due to the module nesting structure; hence:

    class Foo
      MAGIC_NUMBER=42

      def self.hello1
        MAGIC_NUMBER
      end
    end

    def Foo.hello2
      MAGIC_NUMBER
    end

    Foo.hello1 #=> 42
    Foo.hello2 #=> NameError: uninitialized constant MAGIC_NUMBER
and worse:

    Bar = Class.new do
      MAGIC_NUMBER = 99
    end
defines MAGIC_NUMBER as a top-level constant, not as Bar::MAGIC_NUMBER, which is bad enough in itself, but now

    Foo.hello2 #=> 99
which is the kind of subtle misbehaviour that drives people nuts trying to resolve.

Without going into the arcane detail, there are similarly subtle variations that'll show up, involving the default definee (aka the third implicit context), and closures contained in the class definitions.

So I'm very sparing in my use of Class.new and even Module.new, I'll restrict them to carefully written framework methods.

> Class method is just a method of objects class

You'd hope. But look at the mechanics of Kernel#class. Objects don't work from a reference to their (apparent) class, the obj->klass pointer doesn't necessarily go there; it references the head of the linked list of all ancestors, and if you've referred to the singleton in any fashion it'll point to that (absent funny business like prepending the singleton). Then rb_class_real has to iterate along the list skipping the singleton and any ICLASS entries, and assumes the first thing it sees otherwise is the class you meant.

The point being that an object's apparent class is defined by the first object in its ancestors list that isn't its singleton class or a included/prepended module. In theory, this should be invariant across the object's lifetime. In practice, Ruby recomputes it each time. The reason for this is that as soon as you reference the singleton of an object, Ruby a) allocates it, and b) updates the obj->klass pointer to be the singleton, not the class it was made from.

Also, you can screw with people's assumptions via def foo.class; Object; end, which just demonstrates how wilfully ignoring the Law of Demeter gets you into trouble.

It's also what gives you this funky syntax:

    class Foo
      class << self
        def bar; end
      end
    end