Is it possible to give a sub-module the same name as a top-level class?

匿名 (未验证) 提交于 2019-12-03 01:30:01

问题:

Background:

Here's the problem, distilled down to a minimal example:

# 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' 
     

回答1:

Excellent; your code sample is very clarifying. What you have there is a garden-variety circular dependency, obscured by the peculiarities of Ruby's scope-resolution operator.

When you run the Ruby code require 'foo', ruby finds foo.rb and executes it, and then finds foo/bar.rb and executes that. So when Ruby encounters your Foo class and executes include Foo::Bar, it looks for a constant named Bar in the class Foo, because that's what Foo::Bar denotes. When it fails to find one, it searches other enclosing scopes for constants named Bar, and eventually finds it at the top level. But that Bar is a class, and so can't be included.

Even if you could persuade require to run foo/bar.rb before foo.rb, it wouldn't help; module Foo::Bar means "find the constant Foo, and if it's a class or a module, start defining a module within it called Bar". Foo won't have been created yet, so the require will still fail.

Renaming Foo::Bar to Foo::UserBar won't help either, since the name clash isn't ultimately at fault.

So what can you do? At a high level, you have to break the cycle somehow. Simplest is to define Foo in two parts, like so:

# 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 

Hope this helps.



回答2:

Here is a more minimal example to demonstrate this behavior:

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

Output:

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

And here is even more minimal:

Bar = 0 class Foo; end Foo::Bar 

Output:

warning: toplevel constant Bar referenced by Foo::Bar 

The explanation is simple, there is no bug: there is no Bar in Foo, and Foo::Bar is not yet defined. For Foo::Bar to be defined, Foo has to be defined first. The following code works fine:

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

However, there is something that is unexpected to me. The following two blocks behave differently:

Bar = 0 class Foo; end Foo::Bar 

produces a warning:

warning: toplevel constant Bar referenced by Foo::Bar 

but

Bar = 0 module Foo; end Foo::Bar 

produces an error:

uninitialized constant Foo::Bar (NameError) 


回答3:

Here's another fun example:

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

That produces:

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

Ruby (2.3.4) just goes to the first occurrence of "SomeName" it can find, not to the top-level.

A way to get around it is to either use better nesting of modules/classes(!!), or to use Kernel.const_get('SomeName')



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!