I haven't written any ruby for over a decade and a half, when ruby 2.x was still on the horizon. But I do know the source of "eigenclass". It was first jokingly used in an odd instructional programming book / web comic / experimental art piece by "why the lucky stiff" who was briefly prominent in ruby-land then erased himself from the internet. It's funny that it has now become an established term of art for Ruby people.
splat is the official name in Ruby, spread comes from the similar JS construct (obviously, lots of people using Ruby also use JS, so its natural for terminology from one context to sometimes get applied to similar concepts in the other even where it isn’t official.)
I read it after college when I first started working with Rails 1 after I joined a friend’s company. Funny to think that was almost two decades ago. I still remember the quirky art style, although I will admit to preferring a more prosaic book when I learned the language. But it was a fun community and the sense of creativity was really apparent.
> There are at least three ways to define a method in a Ruby singleton class.
It's worth noting that two of such ways will, perhaps unintuitively, ignore the `private` keyword.
private
def self.foo
# ...
end
def x.foo
# ...
end
That will define a public method `:foo` on each of `self`'s and `x`'s singleton classes. If you want the method to be private, either 1) explicitly make the method private using `Class#private` or `Class#private_class_method` or 2) use the `class << self` (or `class << x`) syntax in order for the `private` keyword to work as expected.
private_class_method def self.foo
# ...
end
def self.foo
# ...
end
private_class_method :foo # `def` will return a symbol of the defined method name so the above syntax is usually sufficient.
def x.foo
# ...
end
x.singleton_class.send :private, :foo # Class#private is a private method(!) so must be called using Object#send.
class << x
private
def foo
# ...
end
end
> It's worth noting that two of such ways will, perhaps unintuitively, ignore the `private` keyword.
No, this explanation is wrong, because there isn't actually a `private` keyword, only the Module#private method [0] which, when called with no arguments, changes the visibility of future methods defined in the target module.
Those examples call the `Module#private` method with no arguments in one context changing the visibility of future methods defined in that context, and then use an explicit prefix to define methods in a different context.
When you don't forget that `private` is a private instance method on Module and not a keyword, the behavior is much simpler to understand.
That and how, between block arguments and optional parens on method invocation, Ruby allows (and the core exploits this) defining lots of constructs that look like keyword-based syntax as methods.
I love it when someone dives a bit deeper behind something pretty common for most Ruby devs. It's actually a really elegant pattern that is used and it's everywhere in Ruby.
> In Ruby, every object that is instantiated is represented internally by basically three things:
> * A table of instance values
> * A pointer to the class of the object, used for method lookup
> * A pointer to a unique class for that instance, called the “singleton class” of the object, also used for method lookup
I don't think this is accurate. The source code shows a generic `RObject` [0] contains flags, a pointer to a class (named `klass`), and instance variables. An `RClass` [1] contains flags, `klass`, `super` and a table of methods.
In both cases there is only a single pointer to a class that's used for method lookup.
-
> Ruby will look in the singleton class for the specific user instance first, and will only look for an instance method of the class if there is no matching method in the singleton class.
This is true, but it implies that Ruby does two separate lookups. The way it actually works is more elegant.
An object's `klass` points to its singleton class, and method lookup happens only on that class and its superclasses. The second lookup implied above happens implicitly, because the class of the object is an ancestor of the singleton class.
Specifically, for `class C`, its `klass` points to the singleton class `<< C`, whose ancestors are `<< Object`, `<< BasicObject`, `Class`, `Module`, `Object` and `BasicObject`.
IMO Ruby's whole object model, and in particular the design of this inheritance chain, is beautiful and underappreciated.
-
> I’m not really sure why the Smalltalk solution wasn’t used
I think the way Ruby actually works is closer to the Smalltalk approach than the author realizes.
Having embedded MRI into a variety of side projects over the years, I can confirm your evaluation of Ruby's method lookup procedure is accurate, at least in the context of that legacy, reference implementation of the language. I also agree that Ruby's design is deeply underappreciated.
Don't forget about the Singleton module ("include Singleton"). This one trips me up in terms of what is being referred to as a "singleton". This is more of an singleton class instance where there can only be only one class instance per-class. https://ruby-doc.org/3.4.1/stdlibs/singleton/Singleton.html
> What we’ve been calling “class methods” are actually instance methods of the metaclass.
This also shows up if you ever want to use a Refinement on a “class method”. The thing you must `refine` is the `singleton_class`.
Here's a live example from my UUID library where I refine `Time::now` and one of my own utility class' methods to test the time-went-backwards and network-card-changed cases for incrementing the sequence value while generating time-based UUIDs: https://github.com/okeeblow/DistorteD/blob/3e9bbc744479afd3e...
Noel recommends def self.foo over class << self, implying that these are syntactic equivalents, but I beg to differ; the former does not update the third implicit context (aka the default definee) whilst the latter does, per yugui’s classic article on the matter https://blog.yugui.jp/entry/846, and this makes more sense to me in common usage.
So instead of treating them as syntactic equals when they’re not, I reserve the former as a special case, a clear sign that the following method body was intended to operate in the implicit definition context of its surroundings for some reason.