B contain functions with different names such as
func_of_B respectively, and functions with the same name such as
At first, I only needed to use the function
func_in_both of package
A, so I wrote the following code in the julia script:
Later, I needed to use function
func_of_B, so I added two lines of code to the script:
Then came the trouble. The previous function
func_in_both now become invalid because of a name conflict.
I know now I can change the first function call to
A.func_in_both(), but what if I have called many functions from different packages with conflicting names with functions in
B for many times (i.e., package
B contains many functions that have conflicting names with previously called functions from other packages), and I can’t remember which package each one came from?
If I have to debug previous code from time to time while writing later code, It would hardly be a euphoric programming experience.
Is this due to my improper workflow with Julia (I often suspect that my workflow is not optimal)? Or is there any other measure to deal with this issue?
Thanks for attention!
Code that starts with
is frequent in the Julia ecosystem (and even the docs), but it’s a bad practice because you may forget where the name comes from. That’s why I usually follow BlueStyle, which recommends this instead:
using Package: func
If you apply that to an existing package, it will throw many errors, but at least that way you can debug them one by one until all the names are properly qualified.
import Package as P (where
P is an arbitrary abbreviated name).
Then I use
const to assign further shorthands:
import Package as P
const f = P.full_function_name
const g = ...
If your packages share many names for separate functions (as opposed to contributing methods to shared functions), that is a rare and legitimate headache. In this case, you cannot rely on bare
using .A to access export lists. You should instead start importing specified names, with either
using .A: x or
import .A: x. Those aren’t very similar to bare
import, just think of them as only importing the names to the right of the
:. Note that the name
A is not automatically imported either, you’d have to specify it on the right if it didn’t already exist via a
module block. A couple differences between
import with specified names: 1)
import lets you add methods to imported functions without qualifying the module name, 2)
import lets the names show up in
names(A; imported=true). To resolve conflicting names, you can qualify them with the module name or rename them for the import, whichever is neater. Note that unlike assignments, renamed imports don’t behave as separate variables; the original name and new name will always bind to the same instance, no matter which is reassigned directly.
julia> module A export x; x=0 end # creates module name so no need to import
julia> module B export x; x=1 end
julia> using .A, .B # bad
WARNING: both B and A export "x"; uses of it in module Main must be qualified
ERROR: UndefVarError: `x` not defined
julia> A.x, B.x # if you prefer to specify only the module names
julia> using .A: x as Ax # if you prefer renaming specified names
julia> using .B: x as Bx
julia> Ax, Bx
julia> A2x = Ax # assign new variable
julia> setglobal!(A, :x, 5) # reassign original name in its module
julia> A.x, Ax, A2x # rename was reassigned too, new variable was not
(5, 5, 0)
Since it sounds like you’re struggling with far more than the occasional name conflict, you can check overlap between 2 modules’ export lists:
julia> intersect(names(A), names(B))
That’s one reason why it’s bad but the major problem is really that your code can break due to name conflicts after updating dependencies, as outlined in the original post.
using is fine for exploratory REPL work and throwaway scripts but it has no place in libraries and other long-term code.
Thank you! Now I find that it may take a little more effort to use Julia gracefully.
Thank you for your detailed answer!