How to divide a project into files and submodules?

Thanks, this seems like probably a little more of an elegant solution. If working on a large project, is the best solution here to create a module with defined structs then include them in other appropriate modules?

It’s really hard to answer that kind of question in the abstract, and it’s not much different than other questions about code re-use. You typically break a package into subpackages when individual parts are useful by themselves (or in smaller subsets), and break a module into submodules when you start running into name collisions (though tastes differ); this is independent of when you break a file into multiple smaller files (which can all be in a single module).

Regardless of how you break your code into files, modules, and packages, the main thing is to avoid copy-paste code: keep an eye out for repeated code patterns and find ways to unify them under a single abstraction (functions and data structures).

4 Likes

Include the module file in your overall program once, then import, don’t include, the types’ names from that module into other modules. include in Julia just evaluates source code into a module, and if that happens across separate modules, you make independent duplicates. For example, if I include a file with the text struct X end into modules A and B separately, then the types A.X and B.X have nothing to do with each other. Julia doesn’t have the header-source separation in other languages that allows their include mechanisms to reuse implementations.

If it’s warranted to make a module into an independently developed and installed package, then this include-once principle effectively occurs when you add that package to an environment, so all you have to write are the imports.

2 Likes

Maybe you can help a bit further. Should I use include in my main file? If I set it up as follows, this does not work:

my_structs.jl :

module my_structs
    struct Prod1 end
    struct Prod2 end
    export(Prod1)
    export(Prod2)
end

functions.jl :

module test_functions
    import .my_structs
    
    function md2(x,y,p::Prod1)
        println(x+y)
    end

    function md2(x,y,p::Prod2)
        println(x*y)
    end

    export(md2)
end

main.jl :

module main_moduel
include(".\\my_structs.jl")
using .my_structs
include(".\\functions.jl")
using .test_functions

md2(4,5,Prod1())
end
ERROR: LoadError: UndefVarError: `Prod1` not defined in `Main.test_functions`

If I use include / using in functions.jl then I can get it to load, but it gives me the following error when trying to call the function from main.jl :

ERROR: MethodError: no method matching md2(::Int64, ::Prod1)
1 Like

Should be using ..my_structs — you need .. because it’s being imported by the enclosing module, and you need using because you want to use the unqualified exported symbols.

PS. export is syntax, not a function. You don’t need parens — you can just do export Prod1, Prod2, for example.

PPS. include(".\\my_structs.jl") can just be include("my_structs.jl") — it’s automatically relative to the directory of the current file.

PPPS. In the example here I would generally not bother with submodules at all. There is no need to have a new module for every file, and most projects do not do this. See also Best practise: organising code in Julia - #2 by stevengj

3 Likes

@stevengj mentioned the most important points, but I would like to add one more detail: If you are the only person using the code, then you are free to name identifiers as you wish, but personally I still prefer to follow the naming conventions suggested in the manual as this will make it easier to read other peoples code and code examples. In particular, I would suggest using CamelCase for modules and types.