【问题标题】:Ruby Class namespacing with modules: Why do I get NameError with double colons but not module blocks?带有模块的 Ruby 类命名空间:为什么我会得到带双冒号的 NameError 而不是模块块?
【发布时间】:2023-04-01 14:57:02
【问题描述】:

我正在处理大量预先存在的文件、类和模块,并试图为框架的不同组件提供更好的命名空间。我一直使用模块作为命名空间的一种方式,主要是因为这似乎是标准约定(并且能够“包含”框架的不同部分可能很有用)。

问题是全局命名空间下有大量的类应该存在于模块下。例如,假设有一个类被简单地定义为:

class FirstClass
  def meth
    puts "HELLO"
  end
end

但现在我想在一个模块中包含这个类:

使用双冒号:

module Foo; end

class Foo::FirstClass
  def meth
    puts 'HELLO'
  end
end

使用模块块:

module Foo
  class FirstClass
    def meth
      puts 'HELLO'
    end
  end

使用双冒号更简洁,也更容易实现,因为我正在更改许多类定义。这两种方式都有效,我认为它们实际上是同一件事,但显然它们不是。与模块块相比,双冒号方法似乎导致每个类中的命名空间不同。例如,在“Foo”下面有两个类:

使用模块块:

module Foo
  class FirstClass
    def meth
      puts 'HELLO'
    end
  end

  class SecondClass
    def meth
      FirstClass.new.meth
    end
  end
end

Foo::SecondClass.new.meth

使用双冒号:

module Foo; end

class Foo::FirstClass
  def meth
    puts 'HELLO'
  end
end

class Foo::SecondClass
  def meth
    FirstClass.new.meth
  end
end

Foo::SecondClass.new.meth

代码在使用模块块时有效,但不适用于双冒号。使用双冒号会引发 NameError,因为它将 FirstClass 解析为不存在的 Foo::SecondClass::FirstClass(而不是 Foo::FirstClass)。

这可以通过在SecondClass 中包含Foo 轻松解决,但为什么默认情况下不这样做呢?

注意:我使用的是 Ruby 2.1.5,我知道它已经过时了,但我在 repl.it 上使用 ruby​​ 2.5.5p157 得到了相同的结果:https://repl.it/@joep2/Colon-vs-Block-Namespacing

【问题讨论】:

  • 如果在“模块块”的情况下,插入行puts "constants = #{constants}" 作为模块定义的最后一行,您将看到显示constants = [:SecondClass, :FirstClass]。这告诉您,在模块定义中,您可以引用不带双冒号的类。也就是说,FirstClass.new.methFoo::FirstClass.new.meth 一样(也可以使用)。
  • @CarySwoveland 我知道Foo::FirstClass.new.meth 语法,但我想避免这样做,因为我正在修改预先存在的代码,因此更改对 FirstClass 的每个引用(以前是顶级- level class) 会有点费时,而且看起来不是很干净。现在,我只是将include Foo 放在class Foo::FirstClass 的下面以及Foo 中的所有其他类中。

标签: ruby


【解决方案1】:

这似乎违反直觉,但 Ruby 中的持续查找是使用当前的词法范围完成的,即当前的词法嵌套级别(源代码中的位置),而不是语义嵌套级别。

这可以通过检查Module.nesting 来测试,它会打印当前的词法范围:

class Foo::SecondClass
  pp Module.nesting       # -> [Foo::SecondClass]
end

module Foo
  class SecondClass
    pp Module.nesting     # -> [Foo::SecondClass, Foo]
  end
end

由于 Ruby 使用此嵌套级别进行符号查找,这意味着在您尝试在嵌套 [Foo::SecondClass] 中查找 FirstClass 的情况下,Ruby 将找不到它。

但是,当您尝试在嵌套 [Foo::SecondClass, Foo] 中查找它时,它会在 Foo 下找到 FirstClass,就像您预期的那样。

要解决这个问题,您可以这样做:

class Foo::SecondClass
  def meth
    Foo::FirstClass.new.meth
  end
end

现在可以按预期工作,因为您为FirstClass 提供了必要的查找提示,并告诉Ruby 它在Foo 中。

【讨论】:

  • 很好的答案!
  • 感谢您的回答,Module.nesting 方法很有帮助。它当然可以帮助我理解“如何”,但我仍然对“为什么”感到好奇。这不是很直观,特别是因为我之前的理解是使用模块块与双冒号相同(这是大多数资源似乎建议的)。我能找到的唯一其他主题是here,但它仍然没有回答“为什么”
  • @max 显然,是的,我不明白它是如何工作的,也没有正确搜索我需要的东西。这是一个很好的资源,所以感谢分享。然而,风格指南仍然没有回答“为什么”(excellent article on constant lookup 风格指南也没有参考)
  • 这里是关于这个主题的深入讨论,它还有一个标题为“为什么?”的部分,这可能会有所帮助:medium.com/@hoffm/a-puzzle-about-ruby-constants-e958d15dbada
猜你喜欢
  • 2016-05-17
  • 2015-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 2021-03-20
  • 2021-02-06
  • 1970-01-01
相关资源
最近更新 更多