Is it possible to split a module across multiple files?

Here are two files showing how one might split a module across two files:

# ModuleA1.jl
module ModuleA

function functionA1()
    println("functionA1")
end

end
# ModuleA2.jl
module ModuleA

function functionA2()
    println("functionA2")
end

end

Running the following code will produce a warning stating that ModuleA has been replaced:

include("ModuleA1.jl")
include("ModuleA2.jl")

This suggests to me that one cannot split a module across multiple files.


Having said this, I am aware you can perform a kind of work-around using another include statement.

For example:

# ModuleA1.jl

module A

include("ModuleAImpl.jl")

end

However, I don’t consider this to be good design, because whatever code is written into the file ModuleAImpl will not be wrapped by a module A statement, so it is not that obvious that this code belongs to any module.

I suppose one could write module AImpl and create a module inside ModuleAImpl.jl, but it’s not a very consistent and therefore clean design.


There might not be much to say in response to this question, I suppose?

Maybe someone can propose some alternative?

However, I don’t consider this to be good design, because whatever code is written into the file ModuleAImpl will not be wrapped by a module A statement, so it is not that obvious that this code belongs to any module.

Woudln’t it be obvious if it’s in an appropriate directory?

I agree with you, however I have also seen it argued that directory hierarchy should not be used to construct the tree like structure you might expect from a language like Python.

The reasoning for this being that since modules are explicit, rather than implicit as they are in Python, the directory structure is not meaningful in the same way.

I don’t have a particular opinion on this as I haven’t played around with it enough to know which gives a better result.

I’m open to hearing about your experience

The point for Python being that the existence of a subdirectory is what creates the namespace/module structure. Julia doesn’t work this way.

That’s why Julia has the include statement in addition to the using/import statements. (And module.)

Python just has the equivalents for Julia’s using and import which are the various forms of the import statement.

The fact that paths create modules is why Python does not have either module or include.

This is, as far as I’m aware, the only way, yes. Julia doesn’t permit incomplete expressions in a source file, and there is no “split module” that is extended when another file with the same “split module” object is included.

I agree that this obscures the module hierarchy a bit. I don’t see any harm in putting the files associated with a module into the same directory if it helps your organization of the code though. As far as I understand it, it’s not generally discouraged to do that, it’s just that Julia doesn’t create modules implicitly because of that filesystem hierarchy.

Personally, I don’t think there’s a clear “better” between the various approaches here. Both sides have their up & downsides; with the explicit module declaration approach you need to organize your code yourself more, while the implicit organization-through-the-filesystem approach can be confusing for newcomers or when you refactor something and accidentally relied on this closeness and any resulting shadowing behavior. It’s a matter of preference :person_shrugging:

3 Likes

The files with the module block that holds the include statements is generally easy to find through naming conventions, but if you really want to mark a particular file as being intended for includes, you should comment it. Bear in mind that literally splitting one module block across n files will have n-1 files where the module isn’t written, so you would need commenting anyway for the clarity you want.

It is actually possible to firmly designate source code as being intended for a particular module, the way you tried in your first example. @eval has a module argument for where the code is evaluated, so you could surround each file in an @eval Mod begin ... end block, and you could include the files after the module block instead of inside it. However, it’s far more reliable and flexible to place the includes inside the module block itself, not @eval into it after the fact. For one, you could write other code directly into the module block between the includes, no extra files needed. For another, imagine you change the module’s name, now you have to change every file for it to even run.

3 Likes