The subtleties of const_missing

Hello all,

I've run into an issue regarding the use of const_missing. This issue revolves around the fact that these two cases are supposed to behave differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
         from (irb):3

However it seems impossible to detect which case we are in within the const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing "require 'my_module.rb'" by defining a const_missing that performs that require when MyModule is first referenced. Now, suppose we are writing my_class, and wish to include MyModule. So, we put this code in my_class.rb:

class MyClass
   include MyModule
   ...
end

Our const_missing handler is called, and it loads 'my_module.rb', finds the now present MyModule constant, and returns it. Everything is good.

However, let's say we're slightly misguided and do MyClass::MyModule. Since this is not defined, our const_missing handler is once again called. 'my_module.rb' is thus loaded again, (usually to no effect,) and MyModule returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby will produce a warning regarding our misbehaved const_missing. That said, there does not seem to be a way to behave correctly; from inside the const_missing handler there is no way to tell which case the constant is missing in.

One way to 'fix' this is to have our const_missing look in it's parent modules for the missing constant. If we find it there, we know this is a case of A::B. However this only works if the missing constant has been defined in one of the parents. Thus, we can still load ::B using A::B, but afterwards A::B will fail.

So, sorry for rambling on. The real question is: Can const_missing detect if we are in a "A::B" case rather than "module A; B; end" ?

Thanks for your time and any replies,
Regards,
Nicholas Seckar

but why do you need to distinguish?

   harp:~ > cat a.rb
   class Module
     def const_missing c
       puts "const_missing..."
       const_set c, 42
     end
   end

   module A
     p B
   end

   p A::B

   harp:~ > ruby a.rb
   const_missing...
   42

the 'const_missing' hook will continue to get call only so long as it remain
undefined - if your handler merely loads a file but does not actually define
any const then of course it will continue to fire. what, exactly, does your
handler do?

kind regards.

-a

···

On Wed, 16 Nov 2005, Nicholas Seckar wrote:

Hello all,

I've run into an issue regarding the use of const_missing. This issue revolves around the fact that these two cases are supposed to behave differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
       from (irb):3

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing "require 'my_module.rb'" by defining a
const_missing that performs that require when MyModule is first referenced.
Now, suppose we are writing my_class, and wish to include MyModule. So, we
put this code in my_class.rb:

class MyClass
include MyModule
...
end

Our const_missing handler is called, and it loads 'my_module.rb', finds the
now present MyModule constant, and returns it. Everything is good.

However, let's say we're slightly misguided and do MyClass::MyModule. Since
this is not defined, our const_missing handler is once again called.
'my_module.rb' is thus loaded again, (usually to no effect,) and MyModule
returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby will
produce a warning regarding our misbehaved const_missing. That said, there
does not seem to be a way to behave correctly; from inside the const_missing
handler there is no way to tell which case the constant is missing in.

One way to 'fix' this is to have our const_missing look in it's parent
modules for the missing constant. If we find it there, we know this is a
case of A::B. However this only works if the missing constant has been
defined in one of the parents. Thus, we can still load ::B using A::B, but
afterwards A::B will fail.

So, sorry for rambling on. The real question is: Can const_missing detect if
we are in a "A::B" case rather than "module A; B; end" ?

--

ara [dot] t [dot] howard [at] gmail [dot] com
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara

===============================================================================

I have found this type of laziness often leads to errors that are difficult to discover because somewhere else a MyModule was defined leaving the file I really want to use unrequired.

require 'my_module' is really not that hard to type.

···

On Nov 15, 2005, at 8:36 PM, Nicholas Seckar wrote:

Hello all,

I've run into an issue regarding the use of const_missing. This issue revolves around the fact that these two cases are supposed to behave differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
        from (irb):3

However it seems impossible to detect which case we are in within the const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing "require 'my_module.rb'" by defining a const_missing that performs that require when MyModule is first referenced. Now, suppose we are writing my_class, and wish to include MyModule. So, we put this code in my_class.rb:

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

Hi,

At Wed, 16 Nov 2005 13:36:46 +0900,
Nicholas Seckar wrote in [ruby-talk:165996]:

I've run into an issue regarding the use of const_missing. This issue
revolves around the fact that these two cases are supposed to behave
differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
         from (irb):3

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

What about autoload?

  $ echo 'B = 10' > b.rb

  $ irb
  irb(main):001:0> autoload :B, "b"
  => nil
  irb(main):002:0> module A;end
  => nil
  irb(main):003:0> A::B
  NameError: uninitialized constant A::B
          from (irb):3
  irb(main):004:0> module A;B;end
  => 10
  irb(main):005:0> A::B
  NameError: uninitialized constant A::B
          from (irb):5
  irb(main):006:0>

···

from :0
          from :0

--
Nobu Nakada

Nicholas Seckar wrote:

Hello all,

I've run into an issue regarding the use of const_missing. This issue
revolves around the fact that these two cases are supposed to behave
differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
         from (irb):3

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

I'm not sure what you're up to. You define B in a completely different
scope than you use for lookup. Maybe you can give a more real life
example of what you are actually trying to do.

Kind regards

    robert

Hi,

Rails does this very thing. You should check out their const_missing and see if it suffers from the self-same problem.

Devin

Nicholas Seckar wrote:

···

Hello all,

I've run into an issue regarding the use of const_missing. This issue revolves around the fact that these two cases are supposed to behave differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
        from (irb):3

However it seems impossible to detect which case we are in within the const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing "require 'my_module.rb'" by defining a const_missing that performs that require when MyModule is first referenced. Now, suppose we are writing my_class, and wish to include MyModule. So, we put this code in my_class.rb:

class MyClass
  include MyModule
  ...
end

Our const_missing handler is called, and it loads 'my_module.rb', finds the now present MyModule constant, and returns it. Everything is good.

However, let's say we're slightly misguided and do MyClass::MyModule. Since this is not defined, our const_missing handler is once again called. 'my_module.rb' is thus loaded again, (usually to no effect,) and MyModule returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby will produce a warning regarding our misbehaved const_missing. That said, there does not seem to be a way to behave correctly; from inside the const_missing handler there is no way to tell which case the constant is missing in.

One way to 'fix' this is to have our const_missing look in it's parent modules for the missing constant. If we find it there, we know this is a case of A::B. However this only works if the missing constant has been defined in one of the parents. Thus, we can still load ::B using A::B, but afterwards A::B will fail.

So, sorry for rambling on. The real question is: Can const_missing detect if we are in a "A::B" case rather than "module A; B; end" ?

Thanks for your time and any replies,
Regards,
Nicholas Seckar

The loaded file is expected to define the constant; We assume b.rb contains class or module B (or defines it in any other way). If the constant isn't defined by the loaded file then a NameError is raised as if normal.

···

On 15-Nov-05, at 11:57 PM, Ara.T.Howard wrote:

but why do you need to distinguish?

[snip]

the 'const_missing' hook will continue to get call only so long as it remain
undefined - if your handler merely loads a file but does not actually define
any const then of course it will continue to fire. what, exactly, does your
handler do?

The loaded file is expected to define the constant; We assume b.rb contains
class or module B (or defines it in any other way). If the constant isn't
defined by the loaded file then a NameError is raised as if normal.

i understand that. but in your original message you said

However, let's say we're slightly misguided and do MyClass::MyModule. Since
this is not defined, our const_missing handler is once again called.
'my_module.rb' is thus loaded again, (usually to no effect,) and MyModule
returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby will
produce a warning regarding our misbehaved const_missing. That said, there
does not seem to be a way to behave correctly; from inside the const_missing
handler there is no way to tell which case the constant is missing in.

here you will see that wether we reference B from inside M, or as M::B the
handler is called exactly once.

   harp:~ > cat a.rb
   class Module
     def const_missing c
       puts "const_missing called once..."
       const_set c, 42
     end
   end

   module A
     p B
   end

   p A::B

   harp:~ > ruby a.rb
   const_missing called once...
   42

here is another example which loads a file

   harp:~ > cat a.rb
   class Module
     def const_missing c
       puts "const_missing called once..."
       autoload = {
         :B => 'b.rb'
       }
       autoload[c] ? require(autoload[c]) : super
       const_set(c, const_get(c))
     end
   end

   module A
     B
   end

   module A; p B; end
   p A::B

   harp:~ > ruby a.rb
   const_missing called once...
   42

so what __exactly__ are you trying to do in your handler that does not work? i
understand you problem but, as the second example shows, it can easily be
solved without distinguishing the two syntax cases because, afaikt, ruby
handles them precisely the same.

hth.

-a

···

On Wed, 16 Nov 2005, Nicholas Seckar wrote:
   A::B
--

ara [dot] t [dot] howard [at] gmail [dot] com
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara

===============================================================================