Skip to content

Commit 202ec3e

Browse files
committed
Finished modules.md
1 parent c2db21d commit 202ec3e

File tree

1 file changed

+301
-7
lines changed

1 file changed

+301
-7
lines changed

docs/src/lecture_06/modules.md

Lines changed: 301 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,309 @@
11
# Files and modules
22

3-
When writing a code, one may get in a situation, that the code is long and hard to understand. There are three main ways in Julia how to organize the code. The code can be split into
3+
When writing a code, especially in large projects, it is essential to organize code in a meaningful way. There are three main ways how to do it. The first one is to split code into multiple files. The second one is to use modules to create separates global scopes. The last and most advanced is to extract parts of the code that can be generalized into separate packages. All these three approaches can be (and usually are) used together to get even readable code. In this lecture, we describe how to use these three approaches in Julia.
44

5-
1. separate files,
6-
2. modules, or
7-
3. packages.
5+
## Files
86

9-
In this lecture, we will describe how to use these three approaches to split and organize code in a meaningful way.
7+
The first and most basic approach is to split the code into multiple files. Such files have to be of an appropriate type, i.e. it has to be Julia files with `.jl` extension. The code inside the Julia files can be loaded into global scope using the `include` function.
108

11-
## Files
9+
```julia
10+
include("/absolute/path/to/the/file/filename.jl")
11+
include("../relative/path/to/the/file/filename.jl")
12+
```
13+
14+
The `include` function evaluates the contents of the input source file in the global scope of the module where the `include` call occurs. If some file is included multiple times, the file is also evaluated multiple times.
1215

13-
The first and most basic approach is to split the code into multiple files. Such files have to be of the appropriate type, i.e. it has to be Julia files with `.jl` extension.
16+
Using separate files to organize code can be very useful. However, this approach also has many disadvantages. The main one is, that we have to pay attention to avoid clashing of the variable/function names from different files, since all files are evaluated in the same global scope. This problem can be solved by using modules as described in the following section.
1417

1518
## Modules
19+
20+
Modules allows user to specify what names of variables/functions/types can be visible outside of the module. As we briefly mentioned in the section [Scope of variables](@ref Scope-of-variables), modules in Julia introduce a new global scope. In other words, modules in Julia are separate variable workspaces and provide the following key features
21+
22+
- defining top-level definitions (aka global variables) without worrying about name conflicts when your code is used together with somebody else's,
23+
- control of the visibility of variables/functions/types outside of the module via exporitng,
24+
- control of the visibility of variables/functions/types from other modules inside the module via importing.
25+
26+
The basic syntax for defining modules is following. Modules are created using the `module` keyword.
27+
28+
```@example modules
29+
module Points
30+
31+
using LinearAlgebra
32+
import Base: show
33+
34+
export Point, distance
35+
36+
struct Point{T <: Real}
37+
x::T
38+
y::T
39+
end
40+
41+
coordinates(p::Point) where T = (p.x, p.y)
42+
show(io::IO, p::Point) = print(io, coordinates(p))
43+
distance(p::Point, q::Point) = norm(coordinates(q) .- coordinates(p), 2)
44+
45+
end
46+
nothing # hide
47+
```
48+
49+
In the example above, we define module `Points` in which we define custom type `Point` representing a point in two-dimensional Euclidean space. We also define three functions: `coordinates`, `show`, and `distance`. The `coordinates` function is only an auxiliary function that extracts point coordinates and returns them as a tuple. The `show` function defines a custom show for the `Point` type. Note that we use `import Base: show` to allow adding methods to the `Base.show` function. Finally, the `distance` function simply computes the euclidian distance using the `norm` function from the `LinearAlgebra` module. Note that we have to use `using` or `import` keyword to import other modules. Also, note that we use the `export` keyword to specify which functions/variables/types names are exported.
50+
51+
## `using` and `import`
52+
53+
All functions and types defined inside the `Points` module are defined and accessible in the module's global scope, i.e., inside the module we can use them without any restrictions. If we want to use the functionality outside the module, we must use `using` or `import` keyword. The following table shows some possible ways to use the `using` and` import` keywords.
54+
55+
```@raw html
56+
<table>
57+
<tbody>
58+
<tr>
59+
<th style="text-align: left; vertical-align: middle" vertical-align:middle rowspan="2">Import command</th>
60+
<th style="text-align: center" colspan="2">Available in global scope via</th>
61+
<th style="text-align: center" colspan="2">Available for method extension via</th>
62+
</tr>
63+
<tr>
64+
<th style="text-align: left; vertical-align: middle"><code>&lt;name&gt;</code></th>
65+
<th style="text-align: left; vertical-align: middle"><code>Points.&lt;name&gt;</code></th>
66+
<th style="text-align: left; vertical-align: middle"><code>&lt;name&gt;</code></th>
67+
<th style="text-align: left; vertical-align: middle"><code>Points.&lt;name&gt;</code></th>
68+
</tr>
69+
<tr>
70+
<td style="text-align: left; vertical-align: middle"><code>using Points</code></td>
71+
<td style="text-align: left; vertical-align: middle"><code>Point</code>, <code>distance</code></br> (all exported names)</td>
72+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#399746">everything</p></b></td>
73+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
74+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#399746">everything</p></b></td>
75+
</tr>
76+
<tr>
77+
<td style="text-align: left; vertical-align: middle"><code>import Points</code></td>
78+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
79+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#399746">everything</p></b></td>
80+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
81+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#399746">everything</p></b></td>
82+
</tr>
83+
<tr>
84+
<td style="text-align: left; vertical-align: middle"><code>using Points: </br> Point, coordinates</code></td>
85+
<td style="text-align: left; vertical-align: middle"><code>Point</code>, <code>coordinates</code></td>
86+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
87+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
88+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
89+
</tr>
90+
<tr>
91+
<td style="text-align: left; vertical-align: middle"><code>import Points: </br> Point, coordinates</code></td>
92+
<td style="text-align: left; vertical-align: middle"><code>Point</code>, <code>coordinates</code></td>
93+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
94+
<td style="text-align: left; vertical-align: middle"><code>Point</code>, <code>coordinates</code></td>
95+
<td style="text-align: left; vertical-align: middle"><b><p style="color:#ca3c32">nothing</p></b></td>
96+
</tr>
97+
</tbody>
98+
</table>
99+
```
100+
101+
```@setup modules1
102+
module Points
103+
104+
using LinearAlgebra
105+
import Base: show
106+
107+
export Point, distance
108+
109+
struct Point{T <: Real}
110+
x::T
111+
y::T
112+
end
113+
114+
coordinates(p::Point) = (p.x, p.y)
115+
show(io::IO, p::Point) = print(io, coordinates(p))
116+
distance(p::Point, q::Point) = norm(coordinates(q) .- coordinates(p), 2)
117+
118+
end
119+
```
120+
121+
```@setup modules2
122+
module Points
123+
124+
using LinearAlgebra
125+
import Base: show
126+
127+
export Point, distance
128+
129+
struct Point{T <: Real}
130+
x::T
131+
y::T
132+
end
133+
134+
coordinates(p::Point) = (p.x, p.y)
135+
show(io::IO, p::Point) = print(io, coordinates(p))
136+
distance(p::Point, q::Point) = norm(coordinates(q) .- coordinates(p), 2)
137+
138+
end
139+
```
140+
141+
```@setup modules3
142+
module Points
143+
144+
using LinearAlgebra
145+
import Base: show
146+
147+
export Point, distance
148+
149+
struct Point{T <: Real}
150+
x::T
151+
y::T
152+
end
153+
154+
coordinates(p::Point) = (p.x, p.y)
155+
show(io::IO, p::Point) = print(io, coordinates(p))
156+
distance(p::Point, q::Point) = norm(coordinates(q) .- coordinates(p), 2)
157+
158+
end
159+
```
160+
161+
```@setup modules4
162+
module Points
163+
164+
using LinearAlgebra
165+
import Base: show
166+
167+
export Point, distance
168+
169+
struct Point{T <: Real}
170+
x::T
171+
y::T
172+
end
173+
174+
coordinates(p::Point) = (p.x, p.y)
175+
show(io::IO, p::Point) = print(io, coordinates(p))
176+
distance(p::Point, q::Point) = norm(coordinates(q) .- coordinates(p), 2)
177+
178+
end
179+
```
180+
181+
Consider the following example to help us describe the differences in the use of the `using` and` import` keywords. The goal is to import the `Points` module in one of the ways described in the table above and perform the following four steps.
182+
183+
1. Create an instance `p` of the `Point` type representing point `(4,2)`.
184+
2. Redefine the `coordinates` function to return an array instead of a tuple. Note that this step will change how the `Point` is printed.
185+
3. Create an instance `q` of the `Point` type representing point `(2,2)`.
186+
4. Compute the distance between point `p` and `q` using the `distance` function.
187+
188+
The most common way how to import modules is to use the `using` keyword and the name of the module. Note that packages are imported in the same way as modules. The reason is that the core of each package is a module as will be described later in this lecture. As can be seen in the table above, if we use `using .Points` only the exported names are brought to the global scope, i.e., we can call the `Point` type and the `distance` by their names. However, to redefine the the `coordinates` function, we have to use `Points.coordinates` syntax.
189+
190+
```@repl modules1
191+
using .Points
192+
193+
p = Point(4,2)
194+
195+
Points.coordinates(p::Point) = [p.x, p.y]
196+
197+
q = Point(2,2)
198+
199+
distance(p, q)
200+
```
201+
202+
Note that we use a dot before the name of the module after the `using` keyword. The reason for that is described later. Also, note that the print style changed after the redefinition of the `coordinates` function, since the `show` function for the `Point` type uses the `coordinates` function.
203+
204+
If we use `import .Points`, no names from the `Points` module are brought into the global scope. However, all names are available for usage and extension via `Points.<name>` syntax. The previous example can be rewritten as follows.
205+
206+
```@repl modules2
207+
import .Points
208+
209+
p = Points.Point(4,2)
210+
211+
Points.coordinates(p::Points.Point) = [p.x, p.y]
212+
213+
q = Points.Point(2,2)
214+
215+
Points.distance(p, q)
216+
```
217+
218+
The keyword `import` can be used to avoid name conflicts.
219+
220+
221+
If we want to use only some functions from the module and do not import all of them, we can use the `using` keyword and specify which names should be imported. In such a case these names can be used directly, however, it is not possible to extend functions using this approach.
222+
223+
```@repl modules3
224+
using .Points: Point, distance
225+
226+
p = Point(4,2)
227+
q = Point(2,2)
228+
229+
distance(p, q)
230+
```
231+
232+
In the same way, we can also use the `import` keyword that allows method extension for imported names.
233+
234+
```@repl modules4
235+
import .Points: Point, coordinates, distance
236+
237+
p = Point(4,2)
238+
239+
coordinates(p::Point) = [p.x, p.y]
240+
241+
q = Point(2,2)
242+
243+
distance(p, q)
244+
```
245+
246+
To summarize the above examples, we can say, that in most cases it is sufficient to use the `using` keyword with the name of the module. The `import` keyword followed by the name of a module and a list of names that should be imported is usually used when we want to extend functions.
247+
248+
## Relative and absolute module paths
249+
250+
In the previous section, we added dots before the module name when used after the `using` or `import` keyword. The reason is, that if we try to import a module, the system consults an internal table of top-level modules to look for the given module name. If the module does not exist, the system attempts to `require(:ModuleName)`, which typically results in loading code from an installed package.
251+
252+
If we evaluate a code in the REPL, the code is actually evaluated in the `Main` module which serves as the default global scope. We can check it using the `@__MODULE__` macro that returns the module in which the macro is evaluated.
253+
254+
```julia
255+
julia> @__MODULE__
256+
Main
257+
```
258+
259+
It means, that the `Points` module is actually a submodule of the `Main` module, i.e. it is not a top-level module. It can be seen if we type `Points` in the REPL or if we use the `parentmodule` function that returns a `Module` in which the given module is defined.
260+
261+
```julia
262+
julia> Points
263+
Main.Points
264+
265+
julia> parentmodule(Points)
266+
Main
267+
```
268+
269+
There are two possible ways how to load non-top-level modules: we can use the absolute or relative path to them. In our case, we have the following two options
270+
271+
```julia
272+
using Main.Points
273+
using .Points
274+
```
275+
276+
Adding more leading periods moves up additional levels in the module hierarchy. For example `using ..Points` would look for `Points` in `Main`'s enclosing module rather than in `Main` itself. However, `Main` is its own parent so in this concrete example, the result will be the same.
277+
278+
279+
```@raw html
280+
<div class = "info-body">
281+
<header class = "info-header">Modules and files</header><p>
282+
```
283+
284+
Files and file names are mostly unrelated to modules. Modules are associated only with module expressions. One can have multiple files per module.
285+
286+
```julia
287+
module MyModule
288+
289+
include("file1.jl")
290+
include("file2.jl")
291+
292+
end
293+
```
294+
295+
It is also perfectly fine to have multiple modules per file.
296+
297+
```julia
298+
module MyModule1
299+
...
300+
end
301+
302+
module MyModule2
303+
...
304+
end
305+
```
306+
307+
```@raw html
308+
</p></div>
309+
```

0 commit comments

Comments
 (0)