Ruby Ruby 常量查找

yangyuqian · 2015年06月16日 · 最后由 iaiae 回复于 2015年06月23日 · 3828 次阅读

引言

在 Ruby 中访问一个常量 A 时,最终是怎么找到的呢?来看几个例子:

例 1:

module A
  CREF = "*** CREF in module A"
end 

module A
  module B
    puts CREF
  end
end

例 2:

module A
  CREF = "*** CREF in module A"
end

module A::B
  puts CREF
end

例 3:

class A
  CREF = "*** CREF in module A"
end

B = Class.new(A) do
  puts CREF
end

以上几个例子基本涵盖了本文将要介绍的解释器中的“常量查找”(Constant Lookup) 机制,这些例子中哪些是可以正确执行的呢?如果报错,具体会报什么错?

这里先不揭晓答案,读者可以自行在 irb console 中去执行这几段例程,看看结果是怎么样的。

Ruby 常量查找

很多开发语言中,“常量”的行为很简单,而 Ruby 中的“常量查找”机制给这个已经司空见惯的概念披上了神秘的面纱。

nesting

nesting 是解释器用来编排 (compose) Class/Module 上下层级关系的,以下的定义方式中 B 获得的 nesting 就是自身以及上层的 namespace(不含 Object,这是一个全局回溯的点),即:

module A
  module B
    Module.nesting == [A::B, A]
  end
end

可以将 nesting 看成一颗多叉树,具体怎么编排树中的节点和具体写法有关,以下的定义方式中 B 获得的 nesting 仅包含自身:

module A::B
  Module.nesting == [A::B]
end

nesting 是解释器维护的一个 Class/Module 私有栈,这个栈的读写有很多限制。如传入的 block 不能读取这个栈(得到一个空数组):

class A
end

B = Class.new(A) do
  def self.hi
    Class.nesting
  end
end

B.hi

class C < A
  puts Class.nesting.join(', ')

  def self.hi
    yield
  end
end

C.hi { Class.nesting }

有意思的,用匿名类的方式定义的 singleton 方法读到的 nesting 和直接在 class 里面定义的结果不同,这个匿名类会先被 push 到 nesting 中,然后又 pop 出来:

class A
  class << self
    Class.nesting
  end
end
# -> [#<Class:A>, A]

总之,nesting 和代码的具体写法有关,而且对 block 存在读取限制。

常量查找算法

内核中的常量查找算法的伪代码如下:

if 从 Module.nesting 找到常量定义?
  很开心的退出,返回常量引用
else
  while 遍历到整个结构树的顶层节点?
    if 从 Module.nesting.first.ancestor 找到常量定义?
      很开心的退出,返回常量引用
    else
      继续往 Module.nesting.first.ancestor 查找
    end
  end
end

if 上面的方式找不到
  去 Object 里面查找常量定义,这个时候再找不到就直接抛 NameError 了.
end

现在回过头去看前言中提到的几个例子:

  1. B 中 Module.nesting == [A::B, A],所以可以找到 A::CREF

  2. B 中 Module.nesting == [A::B], 所以找不到任何 A::CREF 定义,抛异常

  3. B 中 Module.nesting == [], 所以就算在 B 中定义了 CREF, block 中也是无法读取的

参考文献

Everything you ever wanted to know about constant lookup in Ruby

Ruby constant lookup: The good, the bad and the ugly

Autoloading and Reloading Constants

module A_m
    MMM='const in A_m'
end
class A
    AAA="const in A"
    include A_m
    class << self
        SSS="const in A_sgl"
    end
end
class Object
    OOO="const in object"
end
class B < A
    puts OOO    #=>get it!
    puts AAA    #=>get it!
    puts MMM    #=>get it!
    Class.nesting.first.ancestors   #=>[B,A,A_m,Object,Kernel,BasicObject]
    class << self
        Class.nesting      #=>[#<Class:B>,B]
        self.ancestors     #=>[#<Class:B>,#<Class:A>,#<Class:Object>,#<Class:BasicObject>,Class,Module,Object,Kernel,BasicObject]
        puts OOO                #get it!
        puts AAA                #can not
        puts SSS                #get it!
    end
end

ruby 新手学习了~谢谢楼主分享~

yangyuqian Rails 中的类加载机制 提及了此话题。 07月29日 10:27
Dounx 请教一个关于 Rails 中常量加载的问题? 提及了此话题。 08月26日 15:43
需要 登录 后方可回复, 如果你还没有账号请 注册新账号