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'
➔ 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
Classes are object-oriented, but modules are not. So only classes can be instantiated as objects.
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.
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.
Modules in Python can be of two types: Built-in Modules. User-defined Modules.
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 include
d.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With