Ruby 常量查找 情景1:
在下面的示例中,Ruby 常量查找 情景1:,ruby,Ruby,在下面的示例中,放置Post::User.foo行打印foo。换句话说,Post::User返回一个全局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 情景2: 第二个示例引发错误,因为找不到常量 module
放置Post::User.foo
行打印foo
。换句话说,Post::User
返回一个全局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
情景2:
第二个示例引发错误,因为找不到常量
module User
def self.foo
"foo"
end
end
module Post
puts Post::User.foo
end
# => uninitialized constant Post::User (NameError)
场景2的结果更直观。为什么会在场景1中找到常数?如果在场景1中返回了User
常量,为什么在场景2中没有发生这种情况?在场景#2中,Post
是一个模块,因此在这种情况下,应该在对象中搜索常量。祖先
,它也应该返回用户
常量,但这不会发生
**ruby中的class
关键字实际上是一个方法名,它接受常量并为其定义一个类
在场景1中,当调用put Post::User.foo
时。Ruby查看是否定义了类Post::User
(Ruby将其作为常量进行搜索,因为它就是这样)。一旦找到它,就会调用foo方法
但是在场景2中,您已经在模块内部定义了它,因为调用了put Post::User.foo
,并且不存在Post::User
这样的类。搜索失败,您会收到明显的错误消息
您可以参考链接中的“类名是常量”部分了解更多详细信息。我认为问题在于类和模块()的继承级别不同。事实上,类对象是从模块对象继承的。被仰视的祖先
module A; end
p A.ancestors #=> [A]
class B; end
p B.ancestors #=> [B, Object, Kernel, BasicObject]
这意味着如果查找算法步进模块A
,则无法步出
因此,在模块Post
的情况下,Post::User
的查找算法如下
- 查找常量
(查找Post
)模块Post
- 在
中查找常量模块Post
(未找到,请查找用户
的祖先)Post
- 死胡同错误
类帖子
- 查找常量
(查找Post
)类Post
- 在
中查找常量类帖子
(未找到,请查找用户
)帖子的祖先
- 查找常量
(查找用户
,带有警告)类用户
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"
它仍然很好。
及
因为查找算法步进了模块,被困住了
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
已更新
在这种情况下,调用Post::User.foo的位置起不了多大作用。它也可能在类/模块之外,其行为将是相同的
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
正如你所指出的
当然,模块或类的祖先不同,但在
模块,则会在Object.Founders中执行额外的常量查找
它只在“初始”查找时发生。这意味着如果
module Post; Post::User.foo; end
constPost
将首先被查找(在这里我可能会出错)Post.concenters
,因为没有Post::Post
,所以继续在Object.concenters
中查找(然后它将使用我在顶部描述的算法)
总结一下,您称之为常量的上下文仅对第一次(最左边)常量查找起作用。然后只考虑剩下的对象
A::B::C
A # consider context
::B # consider only A
::C # consider only B
这种行为是由模块内部而不是类中的查找引起的。(事实上,您正在查找模块内的模块和类内的类是不相关的) 如果您查看C代码,这两个函数都调用了exclude=true、recurse=true、visibility=true 对于A::M,它将查找:
tmp = A
tmp::M # (doesn't exist)
tmp = tmp.super # (tmp = Object)
tmp::M # (exists, but warns).
tmp = B
tmp::M # (doesn't exist)
tmp = tmp.super # (modules have no super so lookup stops here)
在B::M的情况下,它将查找:
tmp = A
tmp::M # (doesn't exist)
tmp = tmp.super # (tmp = Object)
tmp::M # (exists, but warns).
tmp = B
tmp::M # (doesn't exist)
tmp = tmp.super # (modules have no super so lookup stops here)
由于exclude为true,因此跳过了模块跳转到(
tmp=Object
)的常见边缘情况。这意味着B::M
的行为与模块B不同;MReby中的一个不一致性,即: < P>首先,请考虑在类<代码>对象< /代码>中定义所有<强> >顶级< /强>常量,因为Ruby是一种面向对象语言,不存在不属于某类的变量或常量:
class A; end
module B; end
A == Object::A # => true
B == Object::B # => true
其次,类对象
默认为任何类的祖先,但不是模块的祖先:
class A; puts ancestors; end # => [A, Object, Kernel, BasicObject]
module B; puts ancestors; end # => []
同时,模块中都没有对象
。嵌套
:
class A; puts Module.nesting; end # => [A]
module B; puts Module.nesting; end # => [B]
然后,的第7.9章说Ruby在模块.nesting中搜索任何常量,然后在祖先中搜索
因此,在您的示例中,它为类Post
查找常量User
,因为Ruby在类对象中定义了顶级常量User
(如对象::User
)。而对象
是Post
的祖先:
Object::User == Post::User # => true
但是在祖先
或模块中没有对象
。常量Post
在类Object
中定义为Object::Post
,但它不是从Object
派生的,因为模块Post
不是对象。因此,它不会通过它的祖先
解析模块中的常量用户
同时,如果您将User
保留为模块
,并将Post
转换为一个类,则此功能将起作用:
module User
def self.foo
"foo"
end
end
class Post
puts Post::User.foo
end
# => foo
这是因为类Post
可以解析其超类对象中的任何常量,并且所有顶级常量都是在对象中定义的
我怀疑这与模块Post的情况有关。祖先不包括对象
这一事实有关