为什么 Ruby 会用类和函数污染全局命名空间,而不是变量



在test1.rb中拥有这段代码

my_var = 42
def my_func()
  42
end
class MyCLS
  attr_accessor :prop
  def initialize()
    @prop = 42
  end
end

然后在解释器中我需要它在irb中

> require './test1.rb'
> MyCLS.new().prop
  => 42
> my_func()
  => 42
> my_var
NameError: undefined local variable or method `my_var' for main:Object

我很困惑,ruby 似乎很乐意用类和函数污染全局命名空间,但拒绝对 my_var 做同样的事情?我想这是为了避免名称冲突和错误。但是这个问题只得到了部分解决,因为它仍然存在于类和函数中。也许只是不太容易发生?

所以现在想象第二个文件 test2.rb

def my_func()
  43
end
class MyCLS
  attr_accessor :prop
  def initialize()
    @prop = 43
  end
end

然后执行它

> require './test1.rb'
> require './test2.rb'
> MyCLS.new().prop
  => 43
> my_func()
  => 43

以前的全局 MyCLS 和 my_func 被静默覆盖正常吗?这难道不是因为 gem 决定在某处添加/重命名类或函数而破坏软件吗?所有这些似乎都非常脆弱和危险。

我知道模块,我尝试了它们,但收效甚微(尴尬,它们再次是全局的(

有没有办法防止这种情况或减轻语言设计缺陷?

编辑:另一个例子

# test1.rb
def my_func()
  42
end
# test2.rb
puts my_func()
# test3.rb
class Example
  require './test1.rb'
end
class AnotherExample
  require './test2.rb'
end
# command line
$ ruby test3.rb
42

Ruby 中的常量(以大写字母开头的东西(总是在 Object 上创建为常量,除非它们明确是另一个常量的成员(即,module Foo::BarFoo常量下创建一个模块,该常量本身在Object常量下(。

此外,还有一个名为"main"的特殊顶级对象实例。在顶层定义的任何方法在 Object 上都定义为私有方法,因此可以从 main 访问。

require文件时,机制为:

  • 创建新的匿名模块
  • 将请求的文件加载到该模块中
  • 使用该模块扩展main

始终遵守这些规则;您不能在文件中定义顶级方法,然后通过巧妙地放置 require 语句将该文件包含在命名空间中。解析文件后,Ruby 找到一个顶级方法并用它扩展main,而不考虑从何处调用require

如果你想要一个混合到另一个类中

的方法,那么你通常会把它放到一个模块中,然后将该模块混合到你的类中。

# test1.rb
module Bar
  def my_func
    42
  end
end
# test2.rb
require 'test1'
class Foo
  include Bar
end
my_func => # NameError: undefined local variable or method `my_func' for main:Object
Foo.new.my_func # => 42

在 Ruby 中,预计每个文件都将完全命名它打算公开的常量和方法。在大多数真正的 Ruby 项目中,您几乎永远不会编写顶级方法;几乎不用担心事情被无意中覆盖,因为有人需要显式进入你的命名空间才能覆盖内容。

如果你想在不扩展主对象的情况下执行文件,那么你可以使用 Kernel#load 和 wrap 参数,它将加载包装在一个匿名模块中(但使其内部无法访问,除非你在该文件中做了一些事情来公开该文件中的方法和常量(:

load "test1", true
MyCLS # => NameError: uninitialized constant MyCLS

您可以通过自定义加载程序获得这种作用域内的加载:

# test1.rb
def foo
  42
end
# test2.rb
def relative_load(file)
  Module.new.tap {|m| m.module_eval open(file).read }
end
class Foo
  include relative_load("test1.rb")
end
Foo.new.foo  # => 42
foo          # => NameError: undefined local variable or method `foo' for main:Object

顺便说一句,在你的第一个示例中,MyCLS类没有被覆盖;它与现有的MyCLS类合并。因为两者都声明initialize,所以后一个声明优先。例如:

# test1.rb
class MyCLS
  attr_accessor :prop
  # This definition will get thrown away when we overwrite it from test2.
  # It is test2's responsibility to make sure that behavior is preserved;
  # this can be done with reimplementation, or by saving a copy of this
  # method with `alias` or similar and invoking it.
  def initialize(prop)
    @prop = prop
  end
end
# test2.rb
class MyCLS
  attr_accessor :another_prop
  def initialize(prop, another_prop)
    @prop = prop
    @another_prop = prop
  end
end
# test3.rb
require 'test1'
c = MyCLS.new(1, 2) # => ArgumentError: wrong number of arguments (2 for 1)
c = MyCLS.new(1)
c.prop => 1
c.another_prop =>   # => NoMethodError: undefined method `another_prop'
require 'test2'
c = MyCLS.new(1)    # => ArgumentError: wrong number of arguments (1 for 2)
c = MyCLS.new(1, 2)
c.prop => 1
c.another_prop => 2

由于您使用的是全局命名空间,因此我冒昧地说,您正在以JavaScript的背景深入研究Ruby,如果我错了,请纠正我。

Ruby 不会污染全局命名空间,因为当您需要 test_1.rb 或 test_2.rb 等文件时,它们的范围将限制在您需要它们的位置。例如,如果您在名为 Example 的类中需要"test_1",那么如果您在名为 Example 的类中要求"test_2",则无法实现:

Class Example
  require 'test_1'
end
Class AnotherExample
  require 'test_2'
end

您的方法会被覆盖,因为您需要在同一范围内使用这两个文件,而这在较大应用程序的上下文中不会这样做。命名空间可防止您覆盖名称相似的变量、方法等。

my_var 是一个局部变量,其上下文绑定到 test_1.rb。因此,其范围仅限于 test_1.rb 以内。

最新更新