【问题标题】:Is it possible to give a sub-module the same name as a top-level class?是否可以为子模块提供与顶级类相同的名称?
【发布时间】:2011-09-09 09:58:57
【问题描述】:

背景:

这里的问题,提炼成一个最小的例子:

# bar.rb
class Bar
end

# foo/bar.rb
module Foo::Bar
end

# foo.rb
class Foo
  include Foo::Bar
end

# runner.rb
require 'bar'
require 'foo'
➔ ruby​​ runner.rb ./foo.rb:2:警告:Foo::Bar 引用的顶级常量 Bar ./foo.rb:2:in `include': 错误的参数类型 Class (expected Module) (TypeError) 来自 ./foo.rb:2 来自 runner.rb:2:in `require' 来自 runner.rb:2

【问题讨论】:

  • 我只包括 gem,然后它需要库并执行 rails gems 可能执行的任何其他钩子。所以你是说,也许我可以进入供应商提供的 gem 并明确地制作所有内容::Foo?我认为问题出在反面……当我包含User::Foo 时,ruby 首先在顶层搜索Foo……请参阅我在上面更新中提到的线程。

标签: ruby class namespaces module


【解决方案1】:

优秀;您的代码示例非常清楚。你所拥有的是一个普通的循环依赖,被 Ruby 范围解析运算符的特性所掩盖。

当您运行 Ruby 代码 require 'foo' 时,ruby 会找到 foo.rb 并执行它,然后找到 foo/bar.rb 并执行它。所以当Ruby遇到你的Foo类并执行include Foo::Bar时,它会在Foo类中寻找一个名为Bar的常量,因为这就是Foo::Bar所表示的。当它找不到一个时,它会在其他封闭范围中搜索名为Bar 的常量,并最终在顶层找到它。但是那个Bar是一个类,所以不能是included。

即使您可以说服requirefoo.rb 之前运行foo/bar.rb,也无济于事; module Foo::Bar 的意思是“找到常量Foo,如果它是一个类或模块,则开始在其中定义一个名为Bar 的模块”。 Foo 还没有被创建,所以 require 仍然会失败。

Foo::Bar 重命名为 Foo::UserBar 也无济于事,因为名称冲突最终不是问题。

那么可以做什么呢?在高层次上,你必须以某种方式打破循环。最简单的方法是将Foo 定义为两部分,如下所示:

# bar.rb
class Bar
  A = 4
end

# foo.rb
class Foo
  # Stuff that doesn't depend on Foo::Bar goes here.
end

# foo/bar.rb
module Foo::Bar
  A = 5
end

class Foo # Yep, we re-open class Foo inside foo/bar.rb
  include Bar # Note that you don't need Foo:: as we automatically search Foo first.
end

Bar::A      # => 4
Foo::Bar::A # => 5

希望这会有所帮助。

【讨论】:

  • 谢谢大卫——看看我更新的问题,现在有一个最小的例子来说明问题。
  • 嗯......有一种方法可以做到这一点,而无需在 foo/bar.rb 文件中执行 class Foo include Bar end。你可以在 foo.rb 中做class Foo require 'foo/bar' include Foo::Bar end
  • 哦...也将 Foo::Bar 重命名为 Foo::UserBar 确实有帮助(至少 Ruby 1.9)
【解决方案2】:

这里有一个更简单的例子来演示这种行为:

class Bar; end
class Foo
  include Foo::Bar
end

输出:

warning: toplevel constant Bar referenced by Foo::Bar
TypeError: wrong argument type Class (expected Module)

这里甚至更小:

Bar = 0
class Foo; end
Foo::Bar

输出:

warning: toplevel constant Bar referenced by Foo::Bar

解释很简单,没有bug:Foo中没有BarFoo::Bar还没有定义。要定义Foo::Bar,必须先定义Foo。以下代码工作正常:

class Bar; end
class Foo
  module ::Foo::Bar; end
  include Foo::Bar
end

但是,有些事情让我意想不到。以下两个块的行为不同:

Bar = 0
class Foo; end
Foo::Bar

产生警告:

warning: toplevel constant Bar referenced by Foo::Bar

但是

Bar = 0
module Foo; end
Foo::Bar

产生错误:

uninitialized constant Foo::Bar (NameError)

【讨论】:

  • 另一个非常简单的例子:class Foo; include Foo::String; end
【解决方案3】:

这是另一个有趣的例子:

module SomeName
  class Client
  end
end

module Integrations::SomeName::Importer
  def perform
    ...
    client = ::SomeName::Client.new(...)
    ...
  end
end

产生:

block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)

Ruby (2.3.4) 只是转到它可以找到的第一次出现的“SomeName”,而不是顶层。

解决它的一种方法是使用更好的模块/类嵌套(!!),或者使用Kernel.const_get('SomeName')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    • 1970-01-01
    相关资源
    最近更新 更多