常量查找

时间:2015-03-29 01:09:28

标签: ruby

场景#1:

在下面的示例中,puts 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是一个模块,所以在这种情况下,应该在Object.ancestors中搜索常量,这也应该返回User常量,但这不会发生。

4 个答案:

答案 0 :(得分:9)

行为是由模块内部而不是类中的查找引起的。 (你在模块中查找模块并且类中的类是无关紧要的事实。)

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

如果查看C代码,这两个代码都会调用rb_const_get_0,其中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).

在B :: M的情况下,这会查找:

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

因为exclude为true,所以跳过跳转到(tmp = Object)的模块的通常边缘情况。这意味着B::Mmodule B; M; end的行为不同,这可以说是红宝石中的不一致。

答案 1 :(得分:7)

首先,请考虑所有顶级常量都在类Object中定义,因为Ruby是一种面向对象的语言,并且不能有变量或常量不属于某个类:

class A; end
module B; end

A == Object::A # => true 
B == Object::B # => true

其次,类Object默认为任何类的祖先,但不包含模块:

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

与此同时,Object中都没有Module.nesting

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

然后,the Matz book mentioned above的第7.9章说Ruby在Module.nesting然后在ancestors中搜索任何常量。

因此,在您的示例中,它为类User找到常量Post,因为Ruby在类{{1}中定义顶级常量UserObject)。 Object::UserObject祖先

Post

但您的模块Object::User == Post::User # => true 的{​​{1}}或Object中没有ancestors。常量Module.nesting在类Post中定义为Post,但它不是来自Object,因为Object::Post 不是对象 。因此,它无法通过Object解析模块 module Post中的常量User

同时,如果您将Post保持为ancestors并将User转变为某个类,它将会有效:

module

这是因为Post可以解决其内部的任何常量超级module User def self.foo "foo" end end class Post puts Post::User.foo end # => foo ,所有顶级常量都在{{1}中定义}。

答案 2 :(得分:0)

  ruby中的

** class关键字实际上是一个接受Constant并为其定义类的方法名称

如果是方案1,则调用puts Post::User.foo时。 Ruby查看它是否定义了类Post::User(Ruby将其作为常量进行搜索,因为它就是这样)。一旦找到它,就会调用foo方法。

但是在方案2中,您已经在模块中定义了它,因为调用puts Post::User.foo并且不存在Post::User这样的类。搜索失败,您会收到明显的错误消息。

您可以在this链接中引用类名称为常量部分以获取更多详细信息。

答案 3 :(得分:0)

我认为问题在于Class和Module(ascii image)的继承级别。实际上,Class对象是从Module对象继承的。查找的祖先。

module A; end
p A.ancestors   #=> [A]

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

这意味着如果module A中的查找算法步骤,它就无法退出。

因此module Post的{​​{1}} Post::User查找算法类似于

  • 找到const Post(找到module Post
  • User中找到const module Post(找不到,去找Post的祖先)
  • 死胡同 - 错误

以及class Post

的情况
  • 找到const Post(找到class Post
  • User中找到const class Post(找不到,去找Post的祖先)
  • 找到const User(找到class User并发出警告)

这就是为什么你可以在一个命名空间级别链接类,而ruby仍然可以找到它们。如果你试试

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"

仍然很好。 和

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)

因为查找算法在模块中执行并被困。

<强> 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.ancestors中的附加常量查找发生。

在&#34; initial&#34;时刻抬头。这意味着在

的情况下
module Post; Post::User.foo; end
首先会查看

const Post(这里我可能会误解某些内容)Post.ancestors,因为没有Post::Post,继续在Object.ancestors中查找(然后它会请使用我在上面描述的算法。

总结,你所谓的const的上下文只对第一个(最左边)const查找有用。然后只剩下考虑的对象。

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