I have been struggling to understand the nuances of what happens when there are conflicts with names imported from other modules.
I eventually found by myself a consistent explanation for import Foo: bar
. If I’m not mistaken, in practice that’s the same as attempting to bind the content of Foo.bar
to Main.bar
(replace Main
with the module into which you are importing): if Main.bar
already exists, the import is ignored with a warning; and if the name is available, it’s taken. That’s all.
However, with using
things become complicated, and the results depend a lot on the order in which things are done. It is as if using
makes some kind of “lazy import” with the exported names. So, if I load two packages that export shared names, I get this well-known warning when I first try to use the offending name:
julia> module M1
export foo
foo() = 1
end
Main.M1
julia> module M2
export foo
foo() = 2
end
Main.M2
julia> using .M1, .M2
julia> foo()
WARNING: both M2 and M1 export "foo"; uses of it in module Main must be qualified
But if I load one of the modules and use the exported name before loading the other, the first takes the place in Main
:
julia> module M1
export foo
foo() = 1
end
Main.M1
julia> module M2
export foo
foo() = 2
end
Main.M2
julia> using .M1
julia> foo()
1
julia> using .M2
WARNING: using M2.foo in module Main conflicts with an existing identifier.
julia> foo === M1.foo
true
That sort of “lazy import” produces other inconsistencies in certain corner cases. For instance:
If I define something with the exported names in Main
after using
the module, but before calling that name for the first time, it’s as if I had defined it before using
(there is a conflict and the import does not happen), but the warning is missed:
julia> module M1
foo() = 1
end
Main.M1
julia> using .M1
julia> foo() = 2 # No warning
foo (generic function with 1 method)
julia> foo()
2
julia> M1.foo()
1
Besides, if the name is successfully imported, although functions are not available for method extension with just their “raw” names, I can extend them by prefixing not only the name of the original module, but also Main
(or whatever module I’m in):
julia> module M1
export foo
foo() = 1
end
Main.M1
julia> using .M1
julia> foo()
1
julia> foo(x) = 2 # I knew that this is not allowed
ERROR: error in method definition: function M1.foo must be explicitly imported to be extended
Stacktrace:
[1] top-level scope at none:0
[2] top-level scope at REPL[5]:1
julia> Main.foo(x) = 2 # instead of orthodox M1.foo
julia> M1.foo("hi") # This is very confusing, isn't it?
2
julia> Main.foo === M1.foo
true
I found these apparent inconsistencies after having read the documentation about modules, and honestly in my eyes they looked as a somewhat buggy behavior. Upon a second read (after having experimented with this), I realized that the documentation is accurate, and the observed results are consistent. For what is worth, these are the key paragraphs that I had failed to understand:
My question is whether other people think that this is clear enough --so my confusion was a personal failure to understand that information–, or it might be worth to emphasize or rephrase it to make it clearer. Maybe a section explaining conflicts caused by imports?