Skip to content

Commit 1647258

Browse files
authored
Merge pull request #26 from VirtualPlantLab/modules
Add tutorial on modules and files
2 parents 570f3cd + 98a8e4e commit 1647258

2 files changed

Lines changed: 165 additions & 1 deletion

File tree

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ makedocs(;
2222
"Virtual Plant Laboratory" => "index.md",
2323
"Manual" => [
2424
"Julia" => ["Julia basic concepts" => "manual/Julia/Julia.md",
25-
"Multiple dispatch and composition" => "manual/Julia/Objects.md"
25+
"Multiple dispatch and composition" => "manual/Julia/Objects.md",
26+
"Modules and files" => "manual/Julia/Modules.md"
2627
],
2728
"Dynamic graph creation and manipulation" => "manual/Graphs.md",
2829
"Geometry primitives" => "manual/Geometry/Primitives.md",

docs/src/manual/Julia/Modules.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# [Modules and files](@id manual_modules)
2+
3+
In this document we will learn how VPL code can be organized into files and modules.
4+
5+
## Files
6+
7+
Julia code can be organized into files, which are plain text files with a `.jl` extension.
8+
A file can contain multiple functions, types, and variables. We can load the code from a
9+
file using the `include` function. For example, if we have a file named `my_functions.jl`
10+
that contains the following code, we can load this file into the current Julia session using:
11+
12+
```julia
13+
include("my_functions.jl")
14+
```
15+
16+
This will simply execute the code in the file, so it is equivalent to copying and pasting
17+
the code into the current session.
18+
19+
When developing a VPL model you want to be able to makes changes to the code and run it without
20+
having to restart the Julia session. For functions this is not really an issue but redefinition
21+
of types (`struct` or `mutable struct`) is not allowed in Julia within the same session. To
22+
bypass this we need to use the following strategy:
23+
24+
- Make sure that the package `Revise.jl` is installed and loaded.
25+
- Use `includet` instead of `include` to load files.
26+
- Place all type definitions and global variables inside one or more modules.
27+
28+
This means that, when building a typical VPL model, you should include at least one module
29+
to host the type definitions for the different organs in your model. Of course, for large
30+
models you may want to consider splitting your code into multiple files and modules
31+
to keep it organized and manageable.
32+
33+
Note that there are alternative ways to bypass the type redefinition issue, such as using the
34+
[`ProtoStruct.jl`](https://github.com/BeastyBlacksmith/ProtoStruct.jl) package.
35+
36+
## Modules
37+
38+
In Julia, a module is a collection of related functions, types, and variables that can be
39+
grouped together to form a namespace. Modules help in organizing code and avoiding name
40+
clashes. A module can be defined using the `module` keyword, and it can export specific
41+
functions or types to make them available outside the module.
42+
43+
For example, the following code defines a simple module named `MyModule` that exports a function
44+
`my_function`:
45+
46+
```julia
47+
module MyModule
48+
49+
export my_function
50+
51+
function my_function(x)
52+
return x + 1
53+
end
54+
55+
end
56+
```
57+
58+
We can refer to the name of the module as `.MyModule` where the `.` indicates that it is a
59+
local module defined in the current scope (Julia packages also defined modules, but they are
60+
not prefixed with a `.`).
61+
62+
```julia
63+
using .MyModule
64+
result = my_function(5) # This will return 6
65+
```
66+
67+
Modules can also be nested, allowing for better organization of code. For example, we can define
68+
a module `OuterModule` that contains another module `InnerModule`:
69+
70+
```julia
71+
module OuterModule
72+
module InnerModule
73+
export inner_function
74+
function inner_function(x)
75+
return x * 2
76+
end
77+
end # End of InnerModule
78+
export outer_function
79+
function outer_function(x)
80+
return x + 2
81+
end
82+
end # End of OuterModule
83+
```
84+
85+
We can use the nested module as follows (not that `.` is used to separated nested modules):
86+
87+
```julia
88+
using .OuterModule.InnerModule
89+
result_inner = inner_function(3) # This will return 6
90+
using .OuterModule
91+
result_outer = outer_function(3) # This will return 5
92+
```
93+
94+
The keyword `using` will make available all exported functions and types from the module,
95+
while `import` will only make the module available without importing its exported functions
96+
or types. This allows for more control over what is imported into the current namespace but
97+
we need to prefix each function or type with the module name to use it:
98+
99+
```julia
100+
import .OuterModule.InnerModule
101+
result_inner = OuterModule.InnerModule.inner_function(3) # This will return 6
102+
result_outer = OuterModule.outer_function(3) # This will return 5
103+
```
104+
105+
If the name of the module is too long or cumbersome, we can use the `as` keyword to
106+
create an alias for the module:
107+
108+
```julia
109+
using .OuterModule.InnerModule as Inner # Now Inner refers to OuterModule.InnerModule
110+
result_inner = Inner.inner_function(3) # This will return 6
111+
```
112+
113+
Also, we can import specific functions or types from a module using the `import` keyword:
114+
115+
```julia
116+
import .OuterModule.InnerModule: inner_function
117+
result_inner = inner_function(3) # This will return 6
118+
```
119+
120+
A Julia source file can contain multiple modules, but a module can only be defined within a
121+
single source file.
122+
123+
### Defining methods for existing functions
124+
125+
If you want to add a method to an existing function so that it works with a new type (e.g.,
126+
the `feed!` methods that are used in VPL to generate geometry), you need to define the
127+
method by prefixing the function name with the module name where the function is defined.
128+
As you will see in the tutorials, this means we define `feed!` methods as follows:
129+
130+
```julia
131+
function VirtualPlantLab.feed!(turtle::Turtle,, ...)
132+
# Implementation of the method
133+
end
134+
```
135+
136+
It is important to do this to make sure that we are creating a method for the `feed!` function
137+
even if you used `using VirtualPlantLab`.
138+
139+
## How to organize your code
140+
141+
VPL models can become quite complex, depending on how much functionality is added. There is
142+
no correct way to organize your code into multiples files and modules but it helps to think
143+
for a bit why do we even want to do this (i.e., as said before, you probably want to have
144+
at least one modules for your organ types but you could write an entire model in a single
145+
file, as in the tutorials in this website).
146+
147+
We generally want to have multiple files to make sure that, when we are editing a particular
148+
aspect of the code, we visit as few files as possible. The reason for this is that it is
149+
always easier to search within a single file and to jump up and down in the code than to
150+
switch files (though searching across files is also possible in most editors).
151+
152+
We also want to have multiple files if we expect to reuse some of the code for other models
153+
or simpler version of our current model. This touches on the issue of modularity, whereby
154+
we want to organized different parts of the code that can be reused in different contexts.
155+
156+
Regarding modules, the main reason for their use (besides what was described earlier about
157+
types) is to avoid clashes between function names and global variables. This is very much
158+
related to the issue of modularity, as we want to be able to reuse code without worrying
159+
about having to rename functions or variables inside the code (i.e., a module helps build
160+
the abstraction of a *black box* that can be used without knowing its internal details).
161+
This may never be required in small models (or even in large models that are not meant
162+
to be reused), so just use your own judgement to decide how to organize your code based on
163+
your specific needs.

0 commit comments

Comments
 (0)