Ruby解析文件时,为什么常量不像局部变量那样被初始化?

Why are constants not initialized like local variables when the file is parsed by Ruby?

在 Ruby 中,我知道我可以做这样的事情:

if false
  var = "Hello"
end

puts var

应用程序没有崩溃,var 只是设置为 nil。我读到这是由于 Ruby 解析器的工作方式造成的。

为什么这对常量不起作用?

if false
  MY_CONST = "Hello"
end

puts MY_CONST
=> uninitialized constant MY_CONST (NameError)

你永远不会分配常量!将您的代码与这个更扩展的示例进行比较:

if f
   A=5
   B=8
else
   A=9
   C=7
end

请记住,Ruby 中的所有内容都是可执行代码;没有声明。现在如果 ftruthy,你创建常量 AB,如果 ffalsy ,得到常量A和C。

在您的代码中,您有一个 if false,因此不会创建常量。

顺便说一句,常量 这个词在我看来是用词不当,因为您可以更改常量(有或没有警告,取决于您的操作方式)。

从不是Ruby核心成员的实现的角度很难回答这个问题,但从设计的角度来看是完全合乎逻辑的。

if false
  var = 'hello'
end

在这种情况下,您可能想进一步对变量做一些事情,即 尝试用 ||= 重新分配它或根据是否做出一些决定 变量是 nil。并且语言设计将允许这样做,因为这是一个变量(如 不是常量 )。

另一方面,常量是 class 成员并在 class 范围内定义:

initial_constants = self.class.constants

# Does the same as the self.class::MY_CONST = 'hello'
MY_CONST = 'hello'

> self.class::MY_CONST
=> "hello"

> self.class.constants - initial_constants
=> [:MY_CONST]

通常您没有 class 级别的评估。你要么有一个常数,要么没有。

为了确认,在您的问题中提出的简单脚本案例之外,如果您尝试在实例级别以这种方式定义常量:

def hello
  if false
    MY_CONST = 'hello'
  end
end

你会得到异常:

SyntaxError: dynamic constant assignment

现实世界中的大多数 Ruby 程序都是面向对象的。

最后,语言设计者可能必须自己回答的主要问题之一是:

  • 为什么有人需要动态定义的常量?
  • (因此)为什么世界上有人需要一个具有 nil 值的存储常量?

TL;DR

局部变量是在解析器遇到时定义的,而常量则不是。但是,在解释器评估时必须定义两者以避免 NameError。

分析

局部变量由解析器自动激活

您的原始代码实际上并未为局部变量或常量赋值。在这两种情况下,if false 永远不会为真,因此永远不会执行赋值语句。不过,解析器对未定义的变量和常量的处理方式不同。

暂且不谈范围问题,local variables 是在 解析器 遇到赋值时创建的,而不仅仅是在赋值发生时创建的。所以,即使:

if false
  var = "Hello"
end

从不执行赋值,它仍将局部变量初始化为nil

另一方面,常量的处理方式不同。当前命名空间中不可用的未知常量(实际上,任何以大写字母开头的东西)将引发 NameError。

在新的 irb 会话中,这两个都会引发 NameError,但异常消息略有不同:

puts var
#=> NameError (undefined local variable or method `var' for main:Object)

puts MY_CONST
#=> NameError (uninitialized constant MY_CONST)

但是,如果您更改分支逻辑,使带有未定义变量的表达式被解释器求值,您也会得到 NameError:

if baz
  puts true
end

#=> NameError (undefined local variable or method `baz' for main:Object)

检查行为的另一种方法

启动新的 irb 会话。那么:

irb(main):001:0> defined? var
#=> nil
irb(main):002:0> if false then var = 1 end
#=> nil
irb(main):003:0> defined? var
#=> "local-variable"

您可以看到 var 在解析器遇到时被定义并设置为 nil,即使从未评估赋值表达式。常量不是自动激活的,但是:

irb(main):004:0> defined? MY_CONST
#=> nil
irb(main):005:0> if false then MY_CONST = 1 end
#=> nil
irb(main):006:0> defined? MY_CONST
#=> nil
irb(main):007:0> MY_CONST
#=> NameError (uninitialized constant MY_CONST)

结论

虽然我猜想这种行为与解析器和解释器之间的差异有关,也许与用于 variable/method 查找与常量查找的命名空间之间的差异有关,但我无法真正告诉你 为什么 差异是必要的(如果确实如此),或者即使它在所有 Ruby 实现中都相同。这是各个 Ruby 引擎开发人员的问题,包括 Ruby 核心团队。

不过,从实用的角度来说,当您尝试使用 未定义的变量或常量时,您总是会得到 NameError 异常。因此,这种差异对现实世界的影响(如果有的话)很小。所有语言都有怪癖;这可能是其中之一,但很难看出这将如何在人为的示例之外导致实际问题。您的里程肯定会有所不同。