3 ms·
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 in
by inopinatus 6y 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.
- sergeykish 6y ago1. 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
- inopinatus 6y agoIt'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.
- ljm 6y agoIt's also what gives you this funky syntax: class Foo class << self def bar; end end end