|
1 | 1 | # Files and modules
|
2 | 2 |
|
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. |
4 | 4 |
|
5 |
| -1. separate files, |
6 |
| -2. modules, or |
7 |
| -3. packages. |
| 5 | +## Files |
8 | 6 |
|
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. |
10 | 8 |
|
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. |
12 | 15 |
|
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. |
14 | 17 |
|
15 | 18 | ## 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><name></code></th> |
| 65 | + <th style="text-align: left; vertical-align: middle"><code>Points.<name></code></th> |
| 66 | + <th style="text-align: left; vertical-align: middle"><code><name></code></th> |
| 67 | + <th style="text-align: left; vertical-align: middle"><code>Points.<name></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