Proper namespacing and importing local files

I am curious about how to properly organize Julia projects and maintain good name spacing. Now, I realize namespacing is not very important for reducing name collisions as it is in other languages, because of the nature of multiple dispatch. However, I am still a big fan of namespacing for the benefits it provides to us developers.

I have taken a look at existing projects; for example, DataFrames.jl. Their main organization seems to be like so:

include("other/utils.jl")
include("other/index.jl")
...
include("abstractdataframe/sort.jl")
include("dataframe/sort.jl")
...

This immediately strikes me a bad way of designing things. If I want to use a function utils.do_thing in sort, I just call do_thing and give no indication where it came from, hoping that whoever includes both files does it in the proper order.

Here is a simpler example analogous to what I want to do. Suppose I have files A, B, and C. In A, I want to use code from both B and C (while B also depends on C). I would like them to be namespaced, so I know which functions are defined where from just looking at the code. This is the best I can come up with so far, but it does result in “C” being included twice which seems quite incorrect:

# A.jl
module A

include("B.jl")
using .B

include("C.jl")
using .C

B.do_B_stuff()
C.do_thing("C stuff")

end

# B.jl
module B

include("C.jl")
using .C

println("including B")

function do_B_stuff()
    C.do_thing("B stuff")
end

end

# C.jl
module C

println("including C")

function do_thing(thing)
    println("doing ", thing)
end

end

a) Can I do what I am trying to in Julia?
b) Should I do what I am trying to in Julia?

The reason why in Julia “where the function comes from” is not much significant is that the same function can have methods defined in more than one module/package, because of multiple dispatch. You should ask then “where this method comes from”, meaning a specialization of a function for a specific set of types of arguments. That is not really possible, and goes very against the nature of multiple dispatch.

Thus it is much more common that packages are structured in the flat way (includes in proper order only, few modules), as you have observed. You should never “include” a file more than once in a code, thus splitting the code in many modules does not really help the part of the include order (you should not include a file in the main module and then include it again in a submodule that depends on that file).

You can use multiple modules and there is a package, https://github.com/Roger-luo/FromFile.jl, which helps structuring the code in the sense you are thinking.

When a package becomes too large to be structured as a single block, and, more importantly, when a block assumes a set of functionalities that are useful per se, what is natural in Julia is to split the package into more packages, and let the package manager take care of the dependency tree.

(You will find some enormously heated discussions about this theme here in the forums, with more than lengthy discussions of the pros and cons of the way Julia approaches the modularity of codes in this regards)

5 Likes