I want to have a supermodule that contains a function, which carries out different operations depending on the input type. Depending on the type, this function is defined in various submodules. The submodules can be super complicated and standalone but here’s an MWE. Everything is in the LOAD_PATH
This is the supermodule
module SuperModule
include("SubModule1.jl")
include("SubModule2.jl")
end
These are the submodules in their .jl
files in the LOAD_PATH
module SubModule1
export foo
foo(x::Int) = println("Hello there Int")
end
and
module SubModule2
export foo
foo(x::Float64) = println("Hello there Float")
end
now when I do a using SuperModule
I get [ Info: Precompiling SuperModule [top-level]
as expected
However, doing SuperModule.foo(1.0)
or SuperModule.foo(1)
gives an UndefVarError
But of course, SuperModule.SubModule1.foo(1)
and SuperModule.SubModule2.foo(1.0)
produce
Hello there Int
and Hello there Float
I can get the desired behaviour of SuperModule.foo(x)
using include
, without the module
and export
in the submodule files. But how do I get it to work using submodules? I will not be maintaining the packages in the submodules and an include without modules will not work for me in the long run.
Thanks
1 Like
You need to add using .SubModule1, .SubModule2
somewhere in SuperModule
. It is through using
that export
ed names get imported into the current scope.
Example:
julia> module SuperModule
module SubModule
export foo
foo() = println("This is SubModule.foo()")
end
using .SubModule
end
Main.SuperModule
julia> SuperModule.foo()
This is SubModule.foo()
1 Like
Currently both submodules define separate functions that happen to share the name foo
. Multiple dispatch isn’t working because those two foo()
functions have nothing to do with one another, so there’s nothing for dispatch to do.
Fortunately, this is easy to fix. You need to define the function itself in the supermodule (you don’t need to give it any behavior, just declare that it exists), and then you need to explicitly extend that function in each submodule:
julia> module SuperModule
# Define `foo()` but don't give it any methods yet
function foo end
# You can put this into SubModule.jl and do
# include("SubModule.jl") here, but I'm including
# it inline for this simple example.
module SubModule1
# Explicitly indicate that this is the *same* foo as in SuperModule
import ..foo
foo(x::Int) = println("hello Int")
end
module SubModule2
import ..foo
foo(x::Float64) = println("hello Float64")
end
end
Main.SuperModule
julia> SuperModule.foo(1)
hello Int
julia> SuperModule.foo(1.0)
hello Float64
12 Likes
This actually won’t help in the example provided above. Adding using .Submodule1, .SubModule2
will make foo
unusable because both submodules export different functions with the name foo
. You need the import
command inside each submodule so that both submodules will extend the same function rather than defining separate ones.
Right, I thought the question was merely about exporting the names and missed the part about adding methods to a shared function.
Beautiful, that’s quite clean! Since all the submodule packages will need to provide foo, and will be independently maintained, here’s how I followed your idea one step further such that each submodule can be maintained independently of the other ones (except one module file common to all defining an abstract foo
).
module SuperModule
import AbstractModule.foo # is in LOAD_PATH
# could also be through an include
module SubModule1
import AbstractModule.foo # is in LOAD_PATH
foo(x::Int) = println("Hello there Int")
end
# could also be through an include
module SubModule2
import AbstractModule.foo # is in LOAD_PATH
foo(x::Float64) = println("Hello there Float")
end
end
and AbstractModule.jl
is defined as
module AbstractModule
function foo end
end
This works nicely in the REPL
any(pwd() .== LOAD_PATH) || push!(LOAD_PATH, pwd())
using Revise, SuperModule
julia> SuperModule.foo(1)
Hello there Int
julia> SuperModule.foo(1.0)
Hello there Float
But a nagging silly question here - how did SuperModule
know about foo
in any of the submodules? It wasn’t exported explicitly!
Your question is more easily answered if you rewrite
import AbstractModule.foo
foo(x::Int) = println("Hello there Int")
as
AbstractModule.foo(x::Int) = println("Hello there Int")
The second version points out more clearly that AbstractModule.foo(x::Int) = ...
adds a method to the function AbstractModule.foo
. That is, you do not need to export SuperModule.SubModuleX.foo()
because these identifiers are simply aliases for AbstractModule.foo()
.
3 Likes
Ah, ok thanks. I’ve liked your response.