Ah, so here it sounds like you actually were trying to create a multi-“module” solution, and its just that the above MWE was a toy example.
Building a Multi-Module Multi-Package Solution
First, I want to remind you that a Julia “Module” is actually more akin to a C++ namespace.
So if you wish to break down your solution into what is typically known as “modules” in computer science-talk, you need to start talking of Julia “packages”:
Computer Science |
Julia |
C++ |
Arbitrary Scope |
begin … end |
{ … } |
Function-level Scope |
function x() … end |
int x() { … } |
Module-level Scope |
module MyMod … end |
namespace MyNS { … } |
Module |
Package |
.h header file w/compiled library |
Package directory
So the first thing you need to do is find a location to develop your packages. Then, you need to tell Julia where to find them by adding it to the LOAD_PATH
global variable.
You can automatically specify the package location either by setting the shell environment variable JULIA_LOAD_PATH
, or by directly adding to LOAD_PATH
in your ~/.julia/config/startup.jl
file.
Structure of package “library”
The basic structure of a package “library” is as follows:
[path/to/shared/package/root]
├── A
│ └── src
│ └── A.jl
├── B
│ └── src
│ └── B.jl
├── C
│ └── src
│ └── C.jl
├── D
│ └── src
│ └── D.jl
└── M
└── src
├── M.jl
├── logicalunit1.jl
├── ...
└── logicalunitn.jl
Where the logicalunit?.jl
files simply allow you to break down your package implementation with more manageable file sizes.
To access these packages, you would register the above “library” with your ~/.julia/config/startup.jl
file:
push!(LOAD_PATH, "path/to/shared/package/root")
Package definitions & using
/import
In the toy MWE you gave, there is no real need to break things down into logicalunit?.jl
files. So it would now be important to point out that what I previously showed you to was how include
multiple logicalunit?.jl
files to form a single package.
However, when you wish to access the features of a Julia package within your code, you must either call import
or using
(as opposed to include
). This is illustrated in the sample files below.
A/src/A.jl:
module A #Package code must be wrapped inside a "Module"
struct Astruct
#...
end
#more code ...
#Export symbols you don't want the user to
#explicitly qualify with A.Astruct, etc.:
export Astruct
end
B/src/B.jl:
module B
using A #That's right, you don't "include" a package.
#You could also "import A" if you are ok with
#explicitly qualifying "exported" symbols, like
#"A.Astruct" each time!!!
#import A
bfunc(a::Astruct) = dosomething(a)
#If you don't "export" your functions explicitly, there
#is no risk of name collisions, so you could even call
#your function "f" in all your packages or modules!!!!
f(a::Astruct) = dosomething(a)
#more code ...
export bfunc
end
C/src/C.jl:
module C
using A
cfunc(a::Astruct) = dosomething(a)
#more code ...
end
D/src/D.jl:
module D
using A
using B
using C
function dfunc()
a = Astruct()
bresult = bfunc(a)
#Must explicitly qualify cfunc here. It was not exported:
cresult = C.cfunc(a)
return dosomethingwith_bandc_result(bresult, cresult)
end
#more code ...
end
M/src/M.jl:
module M
#Here, you don't need to call "using" on A, B, or C
#because M never accesses those packages directly:
using D
#But you might want to break down package "M"
#across multiple files to make things more manageable:
include("logicalunit1.jl")
#...
include("logicalunitn.jl")
function run_the_fancy_calculation
#setup constants
result = D.dfunc()
#more computations
return result
end
#more code ...
end
That’s it for the most part!