Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Background:

  • ruby thinks I'm referencing a top-level constant even when I specify the full namespace
  • How do I refer to a submodule's "full path" in ruby?

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' 
 ➔ ruby runner.rb ./foo.rb:2: warning: toplevel constant Bar referenced by Foo::Bar ./foo.rb:2:in `include': wrong argument type Class (expected Module) (TypeError)     from ./foo.rb:2     from runner.rb:2:in `require'     from runner.rb:2 
like image 792
John Bachir Avatar asked Jun 06 '11 15:06

John Bachir


People also ask

Are classes and modules the same?

Classes are object-oriented, but modules are not. So only classes can be instantiated as objects.

What are top level modules?

What are top-level modules ? A top-level module is one which contains all other modules. A top-level module is not instantiated within any other module. For example, design modules are normally instantiated within top level testbench modules so that simulation can be run by providing input stimulus.

What is sub module in Python?

How is this done? In my limited experience, modules with submodules are simply folders with a __init__.py file, while modules with functions/classes are actual python files.

What are the different types of modules in Python?

Modules in Python can be of two types: Built-in Modules. User-defined Modules.


2 Answers

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.

like image 150
David Seiler Avatar answered Sep 17 '22 14:09

David Seiler


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) 
like image 33
Alexey Avatar answered Sep 18 '22 14:09

Alexey