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

Bat*_*ler 1 ruby ruby-on-rails

在test1.rb中有这段代码

my_var = 42

def my_func()
  42
end

class MyCLS
  attr_accessor :prop
  def initialize()
    @prop = 42
  end
end
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

def my_func()
  43
end

class MyCLS
  attr_accessor :prop
  def initialize()
    @prop = 43
  end
end
Run Code Online (Sandbox Code Playgroud)

然后执行

> require './test1.rb'
> require './test2.rb'
> MyCLS.new().prop
  => 43
> my_func()
  => 43
Run Code Online (Sandbox Code Playgroud)

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

我知道模块,但尝试得很少成功(尴尬,它们又是全局变量)

有什么方法可以防止这种情况或减轻似乎是语言设计的缺陷?

编辑:另一个例子

# 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
Run Code Online (Sandbox Code Playgroud)

Chr*_*ald 6

Ruby中的常量(以大写字母开头的内容)始终在on上创建为常量Object,除非它们明确地是另一个常量的成员(即,module Foo::Bar在该Foo常量下创建一个模块,而该模块本身就是该Object常量下的)。

此外,还有一个名为“ main”的特殊顶级Object实例。在顶层定义的任何方法都在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
Run Code Online (Sandbox Code Playgroud)

在Ruby中,期望每个文件都将对要公开的常量和方法进行完全命名空间。实际上,您绝不会在大多数真正的Ruby项目中编写顶级方法。几乎不用担心事情会被无意地覆盖,因为有人需要显式地进入您的命名空间来覆盖事物。

如果要在不扩展主对象的情况下执行文件,则可以将Kernel#loadwrap参数一起使用,该参数将负载包装在匿名模块中(但使其内部无法访问,除非您在该文件中进行了某些操作以暴露该文件中的方法和常量):

load "test1", true
MyCLS # => NameError: uninitialized constant MyCLS
Run Code Online (Sandbox Code Playgroud)

您可以通过自定义加载程序获得这种范围的加载:

# 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
Run Code Online (Sandbox Code Playgroud)

顺便说一句,在您的第一个示例中,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
Run Code Online (Sandbox Code Playgroud)