Forum: Ruby Class vs Module in constant lookup

Posted by Peter McLain (Guest)
on 2010-03-03 20:03
(Received via mailing list)
Constant lookup behaves differently if there is a class versus a
module in the name:

     XYZ = 10

     module M
     end

     class C
     end

     p defined? M::XYZ  # => nil
     p defined? C::XYZ  # => "constant"

Is this difference intended? If so, what is the rationale?

--
Peter McLain
peter.mclain@gemstone.com
Posted by Caleb Clausen (Guest)
on 2010-03-03 21:15
(Received via mailing list)
On 3/3/10, Peter McLain <peter.mclain@gemstone.com> wrote:
>
> Constant lookup behaves differently if there is a class versus a
> module in the name:

Extending your example a little to actually look up the constants
instead of just checking if they're defined?:

  XYZ = 10

  module M
  end

  class C
  end

  p defined? M::XYZ  # => nil
  p defined? C::XYZ  # => "constant"

  C::XYZ
#(irb):13: warning: toplevel constant XYZ referenced by C::XYZ
#=> 10

  M::XYZ
# raises NameError


Note the warning for C::XYZ. You really shouldn't look up constants
this way, so I think a little inconsistency like this is tolerable.
Posted by Peter McLain (Guest)
on 2010-03-03 22:57
(Received via mailing list)
On Mar 3, 2010, at 12:15 PM, Caleb Clausen wrote:

>  module M
> #=> 10
>
>  M::XYZ
> # raises NameError
>
>
> Note the warning for C::XYZ. You really shouldn't look up constants
> this way, so I think a little inconsistency like this is tolerable.

   Well, the code isn't mine, but I'd still like to understand if
there is a rationale for the difference in behavior, or if this is
just another MRI implementation detail that's leaked out.  Currently,
MagLev returns "constant" for both classes and modules, but that
breaks third party code.  E.g,:

     return unless defined? RDoc::VERSION # RDoc 1 does not have VERSION
     # code that blows up if RDoc 1 being used...

One could argue that the proper test, which works for both classes and
modules in all ruby implementations, is:

     return unless RDoc.const_defined? :VERSION

But I'd still like to understand if the difference in behavior is
intended or an implementation quirk, and if it is intended, what the
rationale is.

--
Peter McLain
peter.mclain@gemstone.com
Posted by Xavier Noria (fxn)
on 2010-03-03 23:46
(Received via mailing list)
On Wed, Mar 3, 2010 at 8:01 PM, Peter McLain <peter.mclain@gemstone.com> 
wrote:

>
>    p defined? M::XYZ  # => nil
>    p defined? C::XYZ  # => "constant"
>
> Is this difference intended? If so, what is the rationale?

I believe C::XYZ works because C is a subclass of Object.
Posted by Caleb Clausen (Guest)
on 2010-03-03 23:51
(Received via mailing list)
On 3/3/10, Peter McLain <peter.mclain@gemstone.com> wrote:
> modules in all ruby implementations, is:
>
>      return unless RDoc.const_defined? :VERSION
>
> But I'd still like to understand if the difference in behavior is
> intended or an implementation quirk, and if it is intended, what the
> rationale is.

Ok, I see your problem, and I sympathize.

Collisions of the toplevel constant VERSION with VERSIONs defined
inside classes are a repeated snafu. (It's bitten me.) This is
probably why MRI 1.9 no longer defines VERSION, but RUBY_VERSION
instead.

I'd suspect this is unintended, but for an authoritative answer you'll
have to get matz or another core maintainer to chime in. You might ask
on ruby-core; I suspect those guys don't pay a lot of attention to
this list nowadays.
Posted by Peter McLain (Guest)
on 2010-03-04 01:39
(Received via mailing list)
On Mar 3, 2010, at 2:45 PM, Xavier Noria wrote:

>>
>>    class C
>>    end
>>
>>    p defined? M::XYZ  # => nil
>>    p defined? C::XYZ  # => "constant"
>>
>> Is this difference intended? If so, what is the rationale?
>
> I believe C::XYZ works because C is a subclass of Object.

I think that will end up being part of the explanation, but I'm not
yet convinced that it *should* be part of the explanation.

   p M.ancestors  # => [M]
   p C.ancestors  # => [C, Object, Kernel]

The explanation for constant lookup in the Flanagan/Matz book, Section
7.9, starts off:

   "When a constant is referenced without any qualifying namespace..."
and then goes on to explain constant lookup.

Under those rules, I would expect both M and C to find the constant,
and indeed, using unqualified names, they both do find it:

   XYZ = 10

   module M
     p XYZ          # => 10
     p defined? XYZ # => "constant"
   end

   class C
     p XYZ          # => 10
     p defined? XYZ # => "constant"
   end

But the original example was a fully qualified path: M::XYZ and
C:XYZ.  I didn't find any discussion of scoped constants.  The draft
ruby spec, however, leads me to believe that the MRI behavior is a bug.

Section 11.4.3.2 of the spec discusses Scoped constant references.
Under my reading of that section, and our test case, then (following
the logic in that section):

   (a) the primary-expression is M,
   (b) M is a module
   (c 1) XYZ is the constant-identifier
   (c 2) XYZ is not one of the constants defined in M
   (c 3 i) There are no included modules in M (skip)
   (c 3 ii) N/A
   (c 3 iii) Goto step e of section 11.4.3.1

11.4.3.1
   (e) M is not a class
   (e 1) Search Object for a binding for XYZ

So we *should* find it in Object and MRI is incorrect to return nil.

Did I miss something?

--
Peter McLain
peter.mclain@gemstone.com
Posted by Xavier Noria (fxn)
on 2010-03-04 02:08
(Received via mailing list)
On Thu, Mar 4, 2010 at 1:39 AM, Peter McLain <peter.mclain@gemstone.com> 
wrote:

> The explanation for constant lookup in the Flanagan/Matz book, Section 7.9,
> starts off:
>
>  "When a constant is referenced without any qualifying namespace..." and
> then goes on to explain constant lookup.

Yes I also checked the book and albeit it is clear that the algorithm
applies to unqualified names, it is not that much clear what exactly
applies to qualified names.
Posted by Xavier Noria (fxn)
on 2010-03-07 11:35
(Received via mailing list)
On Thu, Mar 4, 2010 at 1:39 AM, Peter McLain <peter.mclain@gemstone.com> 
wrote:

>  (c 3 iii) Goto step e of section 11.4.3.1
>
> 11.4.3.1
>  (e) M is not a class
>  (e 1) Search Object for a binding for XYZ
>
> So we *should* find it in Object and MRI is incorrect to return nil.

Or else the spec needs rewording.

Looking for Object for non-scoped constants in modules seems like a
lexical rule to me, akin to searching Module.nesting.

In a scoped constant reference I wouldn't expect lexical-like steps to
be followed, so it might be the case that M::XYZ does not have to look
in Object, while C::XYZ needs to because of the ancestors rule. That
would a posteriori explain MRI's behavior.

We need an authoritative answer, I saw the question was posted to
ruby-core, let's see.
Posted by Peter McLain (Guest)
on 2010-03-08 18:08
(Received via mailing list)
Since I didn't get a good answer here, I posted the question on ruby-
core, and it looks like I've caught the attention of the core team.
So, if you're interested in the resolution, you should follow:
[ruby-core:28482] Question on scoped constant resolution Class vs Module


On Mar 3, 2010, at 11:01 AM, Peter McLain wrote:

>
>    p defined? M::XYZ  # => nil
>    p defined? C::XYZ  # => "constant"
>
> Is this difference intended? If so, what is the rationale?

--
Peter McLain
peter.mclain@gemstone.com
Posted by Xavier Noria (fxn)
on 2010-03-12 23:49
(Received via mailing list)
On Sun, Mar 7, 2010 at 11:34 AM, Xavier Noria <fxn@hashref.com> wrote:

> would a posteriori explain MRI's behavior.
>
> We need an authoritative answer, I saw the question was posted to
> ruby-core, let's see.

Just a followup, the spec has been indeed revised, M::X does not look in 
Object.
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.