1

Consider the following code:

class MyClass
  def foo_via_method
    foo_method
  end

  def foo_via_constant
    FOO_CONSTANT
  end
end

class SubClass < MyClass
  FOO_CONSTANT = "foo"

  def foo_method
    FOO_CONSTANT
  end
end

The two instance methods here behave differently:

sub_class_instance = SubClass.new

### THIS WORKS ###
sub_class_instance.foo_via_method
# => "foo"

### THIS DOESN'T ###
sub_class_instance.foo_via_constant
# NameError: uninitialized constant MyClass::FOO_CONSTANT

The version that refers to a method in the subclass returns the desired value, but the version that refers to a constant in subclass throws an error. So the puzzle is this: Why does the version that uses a method work but the version that uses the constant fail?

hoffm
  • 2,386
  • 23
  • 36
  • This is an incomprehensible design to me. In OOP I would never expect to be able to look for something defined in a child class except (and my understanding is there's debate on the value of this) a method whose behavior is overridden. Certainly you should never expect something defined only in a subclass to be visible to its parent. – Derrell Durrett Oct 18 '17 at 21:30
  • From me: version that uses method - calls constant in scope of class `SubClass` so it's work, in another one you calls `FOO_CONSTANT` and it trying to find this constant in scope of `MyClass`, to make it workable: just write `SubClass::FOO_CONSTANT` – Oleksandr Holubenko Oct 18 '17 at 22:01

1 Answers1

2

This was a puzzle that I encountered in real production code. I wrote up a detailed explanation of what's going on in this blog post.

Here's the TLDR: Ruby uses a much more complex algorithm for resolving constants than it does for methods. One phase of the constant lookup routine involves looking through the superclass chain for the definition. This phase looks very much like the method lookup routine, deepening the mystery of why methods and constants differ in the way illustrated in the question.

The explanation is that the two superclass chain routines differ in where they start, i.e. which class is the root of the chain.

Method lookup starts with self's class, where self is the receiver of the original method call. In the example, sub_class_instance is the receiver and SubClass is where the lookup starts. SubClass implements foo_method, so all is well.

For constants, Ruby doesn't refer to a receiver, because constant invocations aren't relative to a receiver. Instead, constant superclass lookup begins with the class that's open at the lexical scope where the constant invocation occurs. In the example, the class that's open is MyClass, so thats where Ruby starts to looks for the constant—and never finds it.

hoffm
  • 2,386
  • 23
  • 36
  • 1
    I think it can be summarized as "lexically outwards, then dynamically upwards", but IIRC I found examples where that doesn't actually hold. I don't remember what they were, though. Yugui once wrote a series of articles about the various implicit contexts and lookups in Ruby, but unfortunately, while she mentioned working on an article about constant lookup, that article never appeared. – Jörg W Mittag Oct 18 '17 at 21:09
  • I was amazed how complex constant lookup is when I started digging into YARV. Ruby's slick interface is the result of some gnarly implementation! "Natural, not simple." – hoffm Oct 18 '17 at 21:11