Nested classes and scope - a puzzle
-
I ran into a problem which is echoed in this SO question:
http://stackoverflow.com/questions/9739588/difference-between-class-a-class-b-and-class-ab<span class="syntaxdefault"><br /></span><span class="syntaxkeyword">class </span><span class="syntaxdefault">A<br /> MESSAGE </span><span class="syntaxkeyword">= </span><span class="syntaxstring">"I'm here!"<br /></span><span class="syntaxdefault">end<br /><br /></span><span class="syntaxcomment"># Scope of Object<br /></span><span class="syntaxkeyword">class </span><span class="syntaxdefault">A</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">B<br /> </span><span class="syntaxcomment"># Scope of B<br /> </span><span class="syntaxdefault">puts MESSAGE </span><span class="syntaxcomment"># NameError; uninitialized constant A;;B;;MESSAGE<br /></span><span class="syntaxdefault">end<br /><br /></span><span class="syntaxcomment"># Scope of Object<br /></span><span class="syntaxkeyword">class </span><span class="syntaxdefault">A<br /> </span><span class="syntaxcomment"># Scope of A<br /> </span><span class="syntaxkeyword">class </span><span class="syntaxdefault">B<br /> </span><span class="syntaxcomment"># Scope of B<br /> </span><span class="syntaxdefault">puts MESSAGE </span><span class="syntaxcomment"># I'm here!<br /> </span><span class="syntaxdefault">end<br />end<br /><br /></span>
Why can't
A::B
accessMESSAGE
- but definingA
thenB
will letB
accessMESSAGE
?I also read the article linked in the SO answer (http://yugui.jp/articles/846) but I cannot make sense of my
A::B
is different. -
I've struggled similarly (but with binding and Module.nesting).
It seems that defining
class A::B; end
is not comparable to
class A class B; end end
I have not fully understood why (maybe the first case uses
A
just for namespacing)? -
-
What is confusing is that you use
A::B
to accessB
regardless of which method was used to define it. -
Further more:
<span class="syntaxdefault">module A<br />end<br /><br />module A</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">B<br />end<br /><br />A</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">constants </span><span class="syntaxcomment"># ["B"]<br /><br /></span><span class="syntaxdefault">module A<br /> module C<br /> end<br />end<br /><br />A</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">constants </span><span class="syntaxcomment"># ["C", "B"]<br /> </span><span class="syntaxdefault"></span>
So - while
A::B
is not nested insideA
- it creates a constantB
insideA
. This constantB
refer toA::B
. -
Reference to self - also asked question on Ruby forum: http://www.ruby-forum.com/topic/4404680#1071549
-
Yes it does not seem intuitive at first... because we often forget and think of our constant references as the namespaces themselves, but they are not. They are only reference identifiers, that point AT the actual module or class instance objects. These instances can have any number of references of whatever kind (local, global, constant, @var or @@var,) pointing at them, from any number of separate namespaces.
module Dan # define a pointer to one of TT's constants @@tt_lib_version = TT_Lib;;VERSION # define a local constant pointer to TT's library module; Lib = TT_Lib end
Anyway, I just accept it as some internal C-side Ruby limitation (or design,) and have begin defining properly nested namespaces in my code, at the top of the file, thus:
#{# NAMESPACE DEPENDANCIES # module Outer NESTING = Module.nesting() def self.parent() NESTING[1] ? NESTING[1] ; TOPLEVEL_BINDING end # def ;;parent() module Inner NESTING = Module.nesting() def self.parent() NESTING[1] ? NESTING[1] ; TOPLEVEL_BINDING end # def ;;parent() class InnerMost NESTING = Module.nesting() def self.parent() NESTING[1] ? NESTING[1] ; TOPLEVEL_BINDING end # def ;;parent() end # class InnerMost end # module Inner end # module Outer # #}# class Outer;;Inner;;InnerMost # I can now use ;;parent() method here, # or access the proper nesting via NESTING end # class
ADD: This works for nesting() issues, I'm not sure with outer constant access tho...
-
I did something similar (defining a method "outside") because a nested
eval("Module.nesting", TOPLEVEL_BINDING)
gave not the result that I wanted. -
The issue of namespace nesting is a side issue to the OP issue of constant access.
Back on that issue, there is a nifty solution.
So I am assuming, Thomas, that you wish to share constants among your namespaces, be they modules or classes.
The solution is a Mixin Module of Constants. (I suggest when you set up mixin libraries, that you separate constants, module/class variables, and methods, into separate "mixin" modules. This way when you
include()
one, you know what it is that is being made visible within the "mixee" namespace.)Notice the bold, underlined term above: made visible
When youinclude()
a mixin module (of constants, in this case,) they are made visible in the namespace into which, they are included.
Ruby creates proxy lookups into the actual mixin module.
The constants are not actually defined, nor cloned, nor dup'ed into the "mixee" namespace.
All namespaces that include that mixin module, are truly sharing the SAME constants.*Example, let us say the file is named "TT/Const.rb":
module TT module Const MESSAGE = "I'm here!" end # mixin module Const # add proxy constants to toplevel namespace; include(Const) end # module TT
And perhaps in another file:
module TT;;SomePlugin class SomeNiftyTool require("TT/Const.rb") include(TT;;Const) def activate() UI.messagebox("Message from TT;;Const is, '#{MESSAGE}'") end # the rest of the tool code end # class SomeNiftyTool end # module TT;;SomePlugin
* This fact becomes more important to understand when sharing class/module variables via a mixin module. (Changing their values from ANYWHERE they are included, actually changes them in the common mixin module.)
-
@dan rathbun said:
So I am assuming, Thomas, that you wish to share constants among your namespaces, be they modules or classes.
It was trying to make sense of what I find to be an irregularity. It's even for modules and classes.
<span class="syntaxdefault">module TT_Test<br /><br /> module Foo<br /> CHEESE </span><span class="syntaxkeyword">= </span><span class="syntaxstring">'I am Foo!'<br /> </span><span class="syntaxdefault">module Bar<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">prod<br /> puts CHEESE<br /> end<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">q<br /> p Module</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">nesting<br /> end<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">other<br /> p Biz<br /> end<br /> end </span><span class="syntaxcomment"># Bar<br /> </span><span class="syntaxdefault">def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">q<br /> p Module</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">nesting<br /> end<br /> end </span><span class="syntaxcomment"># Foo<br /><br /><br /><br /><br /> </span><span class="syntaxdefault">module Foo</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Biz<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">prod<br /> puts CHEESE<br /> end<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">q<br /> p Module</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">nesting<br /> end<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">other<br /> p Bar<br /> end<br /> end </span><span class="syntaxcomment"># Foo<br /><br /></span><span class="syntaxdefault">end </span><span class="syntaxcomment"># TT_Test </span><span class="syntaxdefault"></span>
` TT_Test::Foo::Bar.other
TT_Test::Foo::Biz
nilTT_Test::Foo::Biz.other
Error: #<NameError: c:/nested.rb:36:inother': uninitialized constant TT_Test::Foo::Biz::Bar> c:/nested.rb:36 (eval):36
So from one module I can access the sibling-modules/classes - from the other not.
The reason I wanted to understand this deviance is that it affects how I organize my code.
The mixin method you describe might work - depending. But in some scenarios I might just define the modules/classes differently.
-
There is also the
const_missing()
method, which you could override to do some fancy footwork to walk the nesting to find the parent module, then useconst_get()
to return the reference.That is if Mac Ruby 1.8.5-p0 has these methods...
I just find it easier to never assume a sibling can be "seen", and use fully qualified namespace references if I have to.
Sometimes I don't.. like observers. They are easy because we write the constructors, and I always pass the outer namespace in as a
parent
arg tonew()
. -
Another solution, IF the outer module does not contain instance methods, is to include it into the inner module:
module TT_Test module Foo;;Biz include Foo def self.prod puts CHEESE end def self.q p Module.nesting end def self.other p Bar end end # Biz end # TT_Test
I've seen weirder stuff in the standard Ruby Lib extensions, than that.
Advertisement