Ruby:使用class_eval定义常量只能通过const_get找到,而不能直接通过::lookup找到

给定一个 User 类:

class User
end

我想使用.class_eval. 所以:

User.class_eval { AVOCADO = 'fruit' }
User.class_eval { AVOCADO = 'fruit' }
  1. 如果我尝试通过 访问它User::AVOCADO,我得到uninitialized constant User::AVOCADO,但User.const_get(:AVOCADO)有效。为什么?

  2. 如果我在included方法中的 Rails 关注中定义一个常量并将关注包含在User类中,我可以通过常规::查找访问它。例如:

module FruitConcern
  extend ActiveSupport::Concern

  included do
     AVOCADO = 'fruit'
  end

end

class User
  include FruitConcern
end

User::AVOCADO
=> 'fruit'

但是,查找ActiveSupport::Concern的源代码,included只需将该块存储在实例变量 ( @_included_block) 中,然后它append_features就会覆盖will call base.class_eval(&@_included_block)

那么,如果它只是User.class_eval使用同一个块调用,为什么User::AVOCADO在该included块内定义常量时有效,而当我User.class_eval { AVOCADO = 'fruit' }直接调用时却无效?

  1. 奇怪的是(请参阅此博客文章),在执行 时User.class_eval { AVOCADO = 'teste' },Ruby 似乎也将常量泄漏到顶层。所以:
User.const_get(:AVOCADO)
=> "fruit"
AVOCADO
=> 'fruit'

我知道块具有平坦的范围,但是事先没有定义常量,我希望class_evalself和都更改default definee为接收器。这里发生了什么?这个常量是如何在顶层和用户范围内被定义两次的?

回答

Ruby 中有三个隐式上下文:

  • self,“当前对象”:隐式接收器和实例变量的作用域。
  • 默认的 definee:在def bar没有明确目标(即not def foo.bar)的情况下最终定义的方法。
  • 恒定范围。

前两个在链接的文章中得到了很好的解释。在链接的文章中承诺了第三篇文章,但它从未出现过。很多人写过很多关于常量的文字,可惜没有人写出权威的规范,类似于yugui对默认的definee所做的

所以,不幸的是,我也只能推测,从而无益地增加了关于这个话题的大量非权威性词语。

块在 Ruby 中是词法作用域的,它们捕获它们的词法环境。通常,这意味着块内的引用与块外的含义完全相同,就好像块不存在一样。(当然块局部变量除外。)

所以,

foo 
AVOCADO = 'fruit'

{ AVOCADO = 'fruit' }

意思是一样的

AVOCADO = 'fruit'

除非,当然,foo以某种方式改变了块被评估的上下文。我们知道instance_eval变化self。我们知道class_eval更改self默认定义。然而,重要的是:class_eval 改变隐式常量作用域。

因此,AVOCADO = 'fruit'里面的赋值

如果它在块之外,则具有完全相同的含义

换句话说,它与顶层的常量赋值具有相同的含义,正如我们所知,在顶层:

  • self是未命名的单例对象,通常称为main
  • 默认defineeObject,与添加的扭曲是方法成为private默认情况下,和
  • 隐式常量范围也是Object.

所以,AVOCADOObject.

这意味着以下工作:

class User
  AVOCADO
end
#=> 'fruit'

因为常量查找首先在词法上“向外”(在这种情况下它失败),然后在继承层次结构中“向上”,在那里它成功,因为User隐式是Object.

User.const_get(:AVOCADO)
#=> 'fruit'

也有效,因为这实际上与之前的相同,只是通过反射动态完成:它在 class 中启动常量查找算法User,就像你写的一样

class User
  AVOCADO
end

但是,这不起作用:

User::AVOCADO

老实说,这是令人困惑的,因为AVOCADO 应该Object.


回答

当您定义常量时,您并没有为self分配一个常量。您正在当前模块嵌套中定义一个常量。

当您显式打开一个类或模块时,您还设置了模块嵌套:

module Foo
  BAR = 1
  puts Module.nesting.inspect # [Foo]
end

当您执行User.class_eval { AVOCADO = 'fruit' }模块嵌套时,“主要”又名全局对象:

User.class_eval do
  ADVOCADO = 'Fruit'
  puts Module.nesting.inspect # []
end

块实际上不会改变模块嵌套。const_set另一方面在另一个模块嵌套中定义一个常量。

Ruby 实际上也没有两次定义常量。相反,当您使用 const_get 或引用一个常量而不明确使用范围解析运算符时,Ruby 将查看模块嵌套并将树遍历到全局范围:

class A
end
B = 'eureka'
A.const_get(:B) # 'eureka'

这就是如何引用顶级常量而不用::. 但是当您使用时,您User::ADVOCADO明确引用了User.

当涉及到令人担忧的示例时,您误解了正在发生的事情。完全不是关于class_eval. 您正在定义常量FruitConcern::AVOCADO

然后,当您包含FruitConcernUser您正在添加FruitConcern到包含在常量查找中的祖先链中时:

module FruitConcern
  ADVOCADO = 'fruit'
end

class User
  include FruitConcern
end

User::ADVOCADO  # fruit

看:

  • Ruby 模块:包含 vs 前置 vs 扩展
  • 你想知道的关于在 Ruby 中持续查找的一切

以上是Ruby:使用class_eval定义常量只能通过const_get找到,而不能直接通过::lookup找到的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>