翻译 这是我读过关于 constant lookup 讲的最好的文章,不要问我为什么

hiveer · 2015年08月12日 · 最后由 hiveer 回复于 2015年08月13日 · 5637 次阅读
本帖已被设为精华帖!

原文:https://cirw.in/blog/constant-lookup 这是一篇翻译,在此给大家分享,是因为我觉得讲的太好。不知道是不是因为我读的相关书籍太少,还是从来没有 google 过相关知识点,最近看到这篇文章很激动。 它讲的是关于 Ruby 中 Constant lookup 的东西,仔细读了三遍之后,我算是基本了解了 Ruby 的 constant lookup 机制。

在 Ruby 中,当你需要访问一个常量的时候,很简单直接使用这个常量的名字就行。比如:People 但是你知道 Ruby 是怎么去找到People的吗? 简单讲他会去三个地方寻找:

  1. Module.nesting
  2. Module.nesting.first.ancestors
  3. Object.ancestors (条件:当Module.nesting.first.nil?为 true,或者你正处于一个 module 中) 注意: 前两个是没有什么条件的

Module.nesting

我们首先需要知道这是什么,先来一段代码

module A
  module B; end
  module C
    module D
      puts Module.nesting
      B == A::B
    end
  end
end
#A::C::D
#A::C
#A
#=> true

puts的结果来看,你应该能很直观的了解到什么是Module.nesting。但是我想这里可能会产生一个疑问,为什么到了A就没有了? 首先说明我执行这段代码是在普通irb环境,所以相当于是Object的一个实例中。所以A再往上走,就应该到了Object

如果你采用 module 的快捷定义形式,Module.nesting会跳过对应的 namespace

module A
  module B; end
end

module A::C
  B
end
# NameError: uninitialized constant A::C::B

Ancestors

当 Ruby 在Module.nesting中找不到对应的常量的时候,它会在当前打开的 class 或者 module 的 ancestors 中去寻找。

class A
  module B; end
end

class C < A
  B == A::B
end

这里,当前打开 class 或者 module 的概念有点让人迷惑,根据例子来看,我的理解是你访问常量的代码定义所在的 class 或者 module

class A
  def get_c; C; end
end

class B < A
  module C; end
end

B.new.get_c
# NameError: uninitialized constant A::C

Object::

当你发现Module.nesting == []的时候,说明你正处于 top level,也就是说你正处于打开的Object中。根据文章开始列出的三个搜寻地点,第三个Object.ancestors满足了搜寻条件,所以 Ruby 会去Object.ancestors中寻找。 关于Object这里有个重要的假设,如果你当前打开的是一个 module,那么 Ruby 会自动将Object.ancestors加入到你搜寻 list 中

module A; end
module B;
  A == Object::A
end

根据这个假设,以及文章开始列出的搜寻地址 2,我们可以得出一个结论:那就是 top level 的 constant 任何时候都是 accessable 的

class_eval

一般情况下,class_eval 以及它的 block 并不会对 constant lookup 产生什么影响。Ruby 还是会从 block 定义的地方开始去搜寻。

class A
  module B; end
end

class C
  module B; end
  A.class_eval{ B } == C::B
end

然而,当你在 block 中使用的是字符串时,这个字符串执行时对应的Module.nestign将只包含这个 class 本身。

class A
  module B; end
end

class C
  module B; end
  A.class_eval("B") == A::B
end

其他坑

因为单间类的 ancestors 并不包含类本身,所以

class A
  module B; end
end
class << A
  B
end
# NameError: uninitialized constant Class::B

class A
  module B; end
end
class << A; ancestors; end
[Class, Module, Object, Kernel, BasicObject]

欢迎补充

感谢楼主的精彩分享,这篇文章确实极其精彩,我刚好这两天也在反复的读,特此补充,哈哈。

补充 1

这个地方翻译欠妥。查找常量时 Module.nesting 并不是当前的代码定义的 class。

Ruby 有三种 context

  1. self 当前对象

  2. definee 当前类

  3. lexical scope

作者在此处指的是第三种 context: lexical scope。 lexical scope 只和 class 和 module 这两个 keyword 有关。

所以作者最后的结论是两个:

  1. Module.nesting 取决于 lexical scope

  2. lexical scope 只取决于 class、module 两个关键字。(和 class_eval 、instance_eval 无关)

补充 2

原文用的 attach Object ,不是 Open Object ,此处确实很难翻译。

class C

end

在顶级定义一个类 C 时,是把 C 这个常量 attach 到 Object 这个 Rclass 的 constant table 中。

2楼 已删除

#1 楼 @xiaoronglv 你的补充才是这篇分享的一次升华啊,你的理解的确很深入,也很专业。

关于补充 1 对于你例举的三种 context,我的理解如下: 1.self 当前对象

class A
  def m
  end
end

a = A.new
a.m

这里对于方法 m 中的任何操作,a 是当前对象

  1. definee 当前类 class A ... end 这里,我们实际上做了一个打开类的操作。所以,对于我们将要在打开的类中进行的任何操作,A 都是他们的当前类。
  2. lexical scope 说实话,这是我第一次接触这个概念,我对它只有一个表象上的理解,专为Module.nesting而生

然后,回到我的那句话

我的理解是你访问常量的代码定义所在的 class 或者 module

其实我本意是在这里想解释下,当前打开类,也就是你所说的第二种 context 的那种概念。顺便附上原文

The currently open class or module is the innermost class or module statement in the code. A common misconception is that constant lookup uses self.class, which is not true. (btw, it's not using the default definee either, just in case you wondered):

我又小小测试了下

8] pry(main)> module C
[8] pry(main)*   def get_d
[8] pry(main)*     puts Module.nesting
[8] pry(main)*   end
[8] pry(main)* end
=> :get_d
[9] pry(main)> class A
[9] pry(main)*   include C
[9] pry(main)* end
=> A
[10] pry(main)> class B < A
[10] pry(main)* end
=> nil
[11] pry(main)> B.new.get_d
C
=> nil

从此例可以看出来,无论你是 mix in 或者是继承,当你调用get_d的时候,Ruby 都把定义get_d这个方法所在的 module 作为当前打开类。从我目前的理解来看,我觉得我有点混淆了 Ruby 的三种 context,但是想要表达的东西碰巧合适。因为Module.nesting的第一个成员就是当前打开类。

#1 楼 @xiaoronglv 关于补充 2 首先看看我的叙述

当你发现 Module.nesting == [] 的时候,说明你正处于 top level,也就是说你正处于打开的 Object 中

我在这里其实是想解释下,什么是 top level。我的理解是,当你打开 bash,敲入irb or pry,好你已经进入了 top level 了。这个时候:

[12] pry(main)> self
=> main
[13] pry(main)> self.class
=> Object

可以看出,你正处于 Ruby 的第一种 context 中,这个对象是main,而这个对象的 class 就是Object。所以我对 top level 的理解就是,当你处于打开的Object中的时候,你就是处于 top level 了。

而关于attch,我理解这是一个专门针对Module.nesting的概念。

class A
  class B; end
  module C; end
end

这里,我们可以说,因为我们在打开的 class A 中定义了,class B 以及 module C,所以我们可以说,我们把 B 和 C attach 到了 A。

5楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册