How to deal with name conflict between functions from different packages?

Packages A and B contain functions with different names such as func_of_A and func_of_B respectively, and functions with the same name such as func_in_both.

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:

using A

func_in_both()

Later, I needed to use function func_of_B, so I added two lines of code to the script:

using A
using B

func_in_both()
func_of_B()

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

using Package

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.

3 Likes

I prefer 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 = ...
1 Like

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 using/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 using and 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
Main.A

julia> module B export x; x=1 end
Main.B

julia> using .A, .B # bad

julia> x
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
(0, 1)

julia> using .A: x as Ax # if you prefer renaming specified names

julia> using .B: x as Bx

julia> Ax, Bx
(0, 1)

julia> A2x = Ax # assign new variable
0

julia> setglobal!(A, :x, 5) # reassign original name in its module
5

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))
1-element Vector{Symbol}:
 :x
3 Likes

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.

A bare using is fine for exploratory REPL work and throwaway scripts but it has no place in libraries and other long-term code.

5 Likes

Thank you! Now I find that it may take a little more effort to use Julia gracefully. :thinking:

1 Like

Thank you for your detailed answer! :handshake: :handshake: