This question is an extension of this question. The answer helped me understand what was happening, but I am still questioning why.
When defining two classes within a module there are two ways to write it.
Using Module Blocks:
module Foo
class FirstClass
def meth
puts 'HELLO'
end
end
class SecondClass
def meth
FirstClass.new.meth
end
end
end
Foo::SecondClass.new.meth
Using Double Colons:
module Foo; end
class Foo::FirstClass
def meth
puts 'HELLO'
end
end
class Foo::SecondClass
def meth
FirstClass.new.meth
end
end
Foo::SecondClass.new.meth
Both ways work for class definition, but when using double colons you cannot directly lookup FirstClass
inside of SecondClass
without including FirstClass
or writing Foo::FirstClass
. This happens because Foo
is not a part of the lexical scope of SecondClass
when it's defined with double colons, which can be demonstrated by using Module.nesting
.
Why is Foo
not added to the lexical scope with double colons? In the context of the lower level Ruby source code, why does ruby_cref
point only to Foo::SecondClass
instead of ruby_cref
pointing to SecondClass
which then points to Foo
?
For Example:
+---------+ +---------+
| nd_next | <-----+ nd_next | <----+ ruby_cref
| nd_clss | | nd_clss |
+----+----+ +----+----+
| |
| |
v v
Foo SecondClass
Let me ask you the reverse question: why would it?
As you found in the last question, module nesting is important. For a quick intuitive example,
module Foo
puts self # executing in Foo context
module Bar
puts self # executing in Foo::Bar context
end
end
It is only modules (and its subclasses, such as Class
) that can do this — change the execution context by nesting.
Now, over to your examples. The first snippet is effectively equivalent to:
module Foo
# executing in Foo namespace context
FirstClass = Class.new
meth1 = proc do puts "HELLO" end
FirstClass.define_method(:meth, meth1)
SecondClass = Class.new
meth2 = proc do FirstClass.new.meth end
SecondClass.define_method(:meth, meth2)
SecondClass.new.meth
end
Here, assuming we executed this at the main
level, all of the references are relative to ::Foo
. When we write FirstClass
, it is understood as ::Foo::FirstClass
.
The second snippet is effectively equivalent to
# executing in top namespace context
Foo = Module.new
Foo::FirstClass = Class.new
meth1 = proc do puts "HELLO" end
Foo::FirstClass.define_method(:meth, meth1)
Foo::SecondClass = Class.new
meth2 = proc do FirstClass.new.meth end # ERROR
Foo::SecondClass.define_method(:meth, meth2)
Foo::SecondClass.new.meth
Written this way, it might be obvious why the second example does not work. If this was executed in main
, then Foo::FirstClass
that we defined is understood as ::Foo::FirstClass
. The FirstClass
mention in the error line is understood as ::FirstClass
, which was never defined.
It allows you to explicitly set the modules that are used for constant lookup. Here's an example of a class MyClass
defined under a module Foo
:
module Foo
A = 'A in Foo'
B = 'B in Foo'
C = 'C in Foo'
end
module Foo
class MyClass
B = 'B in MyClass'
p Module.nesting #=> [Foo::MyClass, Foo]
def self.abc
[A, B, C]
end
end
end
Foo::MyClass.abc
#=> ["A in Foo", "B in MyClass", "C in Foo"]
The constants are resolved the way Module.nesting
shows, i.e. A
, B
, C
are searched in Foo::MyClass
and then in Foo
.
Now for something more unusual. We can add a totally unrelated module Bar::Baz
in-between Foo::MyClass
and Foo
for constant lookup:
module Foo
A = 'A in Foo'
B = 'B in Foo'
C = 'C in Foo'
end
module Bar
module Baz
C = 'C in Bar::Baz'
end
end
module Foo
module ::Bar::Baz
class Foo::MyClass
B = 'B in MyClass'
p Module.nesting #=> [Foo::MyClass, Bar::Baz, Foo]
def self.abc
[A, B, C]
end
end
end
end
Foo::MyClass.abc
#=> ["A in Foo", "B in MyClass", "C in Bar::Baz"]
I don't know if this has any real-world application, but it makes constant lookup extremely flexible. You can precisely select the modules you want to include (or exclude) for constant lookup and their order.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With