Constant search

Scenario number 1:

In the example below, the line puts Post::User.foo prints foo . In other words, Post::User returns the global constant User .

 class User def self.foo "foo" end end class Post puts Post::User.foo end # => warning: toplevel constant User referenced by Post::User # => foo 

Scenario number 2:

This second example causes an error because a constant was not found.

 module User def self.foo "foo" end end module Post puts Post::User.foo end # => uninitialized constant Post::User (NameError) 

The result of scenario # 2 is more intuitive. Why is the constant found in script # 1? If the User constant is returned in script # 1, why doesn't this happen in script # 2? In scenario # 2, Post is a module, so in this case, the constant should be looked up in Object.ancestors , which should also return the User constant, but this does not happen.

+6
source share
4 answers

The behavior is caused by the fact that the search is performed inside the module, and not in the class. (the fact that you are viewing a module inside a module and a class inside a class does not matter).

 module M def self.foo; "moo" end end class C def self.foo; "coo" end end class A A::M.foo rescue puts $! # warning: toplevel constant M referenced by A::M A::C.foo rescue puts $! # warning: toplevel constant M referenced by A::M end module B B::M.foo rescue puts $! # error: uninitialized constant B::M B::C.foo rescue puts $! # error: uninitialized constant B::C end 

If you look at the C code, both of them call rb_const_get_0 with exclude = true, recurse = true, visibility = true.

In the case of A :: M, this looks like:

 tmp = A tmp::M # (doesn't exist) tmp = tmp.super # (tmp = Object) tmp::M # (exists, but warns). 

In the case of B :: M, this looks like:

 tmp = B tmp::M # (doesn't exist) tmp = tmp.super # (modules have no super so lookup stops here) 

Since the exception is true, the usual edge register for modules jumping on ( tmp = Object ) is skipped. This means that B::M behaves differently with module B; M; end module B; M; end module B; M; end , which may be inconsistency in ruby.

+9
source

First of all, keep in mind that all top-level constants are defined in the Object class, since Ruby is an object-oriented language and cannot be a variable or constant that does not belong to a certain class:

 class A; end module B; end A == Object::A # => true B == Object::B # => true 

Secondly, the Object class is by default the ancestor of any class, but not a module:

 class A; puts ancestors; end # => [A, Object, Kernel, BasicObject] module B; puts ancestors; end # => [] 

At the same time, there is no Object in any of the Module.nesting :

 class A; puts Module.nesting; end # => [A] module B; puts Module.nesting; end # => [B] 

Then, in chapter 7.9 of Matz's book mentioned above , Ruby looks for any constant in Module.nesting , and then in ancestors .

Therefore, in your example, it finds the User constant for the Post class, because Ruby defines the top-level constant of the User class in the Object class (as Object::User ), And Object is the ancestor of Post :

 Object::User == Post::User # => true 

But in ancestors or Module.nesting your Post module does not have an Object . The Post constant is defined in the Object class as Object::Post , but it is not inferred from Object because module Post not an object . Therefore, it does not allow the User constant in the Post module through it ancestors .

At the same time, it will work if you save User as module and turn Post into a class:

 module User def self.foo "foo" end end class Post puts Post::User.foo end # => foo 

That class Post can resolve any constant inside the superclass of Object , and all top-level constants are defined in Object .

+7
source

** class keyword in ruby ​​is actually the name of the method that accepts Constant and defines a class for it

In case of scenario 1, when calling puts Post::User.foo . Ruby looks to see if it has a Post::User class (Ruby looks for it as a constant because that is what it is). Once it finds this, the foo method is called.

But in scenario 2, you defined it inside the modules, because when calling puts Post::User.foo , there is no such class as Post::User . The search fails and an obvious error message appears.

For more information, see the this class name section for a reference.

0
source

The problem, in my opinion, is the difference in the level of class inheritance and the number class ( ascii image ). In fact, an object of a class is inherited from object objects. which are viewed.

 module A; end p A.ancestors #=> [A] class B; end p B.ancestors #=> [B, Object, Kernel, BasicObject] 

This means that if the search algorithm algorithm in module A , it cannot exit.

So, in the case of module Post the Post::User search algorithm is something like

  • find const Post (found module Post )
  • find const User in module Post (not found, go to Post ancestors)
  • dead-end - error

and in case of class Post

  • find const Post (found class Post )
  • find const User in class Post (not found, go to Post ancestors)
  • find const User (found class User with warning)

That's why you can bind classes at the same level of the namespace, and ruby ​​will still find them. If you try

 class User def self.foo "foo" end end class A1; end class A2; end class Foo p Foo::A1::A2::User.foo end #... many of warnings #> "foo" 

still good. AND

 class User def self.foo "foo1" end end class A1; end module A2; end class Foo p Foo::A1::A2::User.foo end #>.. some warnings #> uninitialized constant A2::User (NameError) 

because the steps of the search algorithm in the module were trapped.

TL DR

 class User def self.foo "foo" end end class Post1 Post1.tap{|s| p s.ancestors}::User.foo #=> [Post1, Object, Kernel, BasicObject] end # ok module Post2 Post2.tap{|s| p s.ancestors}::User.foo #=> [Post2] end # error 

Update

In this situation, the place when calling Post :: User.foo does not play a big role. It can also be outside the class / module, and the behavior will be the same.

 module ModuleA; end class ClassB; end class ClassC; end class E; ModuleA::ClassC; end # error module F; ClassB::ClassC; end # ok ClassB::ClassC # ok ClassB::ModuleA # ok ModuleA::ClassB # error 

And as you indicated

Of course, the ancestors for a module or class are different, but in the case of a module, an additional constant search in Object.ancestors is added.

this happens only at the time of the "initial" search. This means that in the case of

 module Post; Post::User.foo; end 

const Post will be considered first (here I can make a mistake) Post.ancestors and because there is no Post::Post , continue to find in Object.ancestors (then it will go with the algorithm that I described above).

To summarize , the context in what you called const is relevant only for the first (leftmost) constant search. And then only the object that is being considered remains.

 A::B::C A # consider context ::B # consider only A ::C # consider only B 
0
source

Source: https://habr.com/ru/post/984372/


All Articles