Functions¶
The code base for big projects is very complex -- possibly tens of thousands of lines of code.
How to make this comprehensible for humans?
A key idea of structured programming:
- package code into self-contained functions
- avoid side effects
A function should only change the rest of the world through the outputs it explicitly returns. This is called encapsulation.
Then we can reason about small pieces of code in isolation. We don't have to worry about what the other code does.
Rule of thumb: All of your code should be inside functions.
Scripts¶
Scripts are simply collections of commands that are run as if they were typed at the command prompt.
Example¶
Create a file called test1.jl
that contains
x = 2;
println("We defined x to be $x");
Now running this file with include("test1.jl")
is exactly the same as typing those lines in the REPL.
julia> include("test1.jl")
We defined x to be 2
julia> x
2
Note that the value of x
"leaks out" of the script -- exactly as if we had typed the code in the REPL.
In computer lingo, the script runs in the global namespace.
- More precisely, everything in the script becomes a global object in the
Main
module. - This is bad!
- Everything is visible everywhere.
To avoid polluting the global namespace, we use functions.
Rule of thumb: Always use functions, never scripts!
Functions¶
Functions are similar to scripts with one crucial difference:
- All variables inside a function are private.
- Other functions cannot see the variables inside a function (encapsulation).
- Any variable a function should know must be passed to it as an input argument.
- Any variable to be retained after the function finishes must be passed out of it as a return argument.
A function looks like this:
function f(x, y)
# Do stuff with x and y -> z
return z
end
It takes two inputs (x, y
), hopefully without modifying them, and affects the rest of the world only through its return value z
.
A side note: Even built-in commands are often written in Julia.
Encapsulation¶
A function creates a local scope or a namespace.
Only items explicitly defined in the namespace are visible there.
Note: Module
s are another way of creating a namespace. We will talk about those soon.
By default, all objects are local: they are only visible inside the current function.
Some objects are explicitly declared global: they are visible everywhere (in the current module
).
Rule: Avoid globals like the plague, unless they are constants.
Example:¶
julia> function f(x)
a = 5;
y = x + a;
return y
end
f (generic function with 1 method)
julia> a = 3;
julia> y = f(0)
5
julia> a # Calling `f` did not change `a` on the "outside"
3
# Now the converse
julia> function g(x)
# Note that `a` is not defined in `g`
z = x + a;
end
g (generic function with 1 method)
julia> g(1)
4 # `g` used the `global a`
Note that g
could see the variable a=3
defined in the REPL, but not the a
inside of f
.
Setting a=3
in the REPL defined a global
variable which is visible everywhere. Defining a variable inside a function produces a local
variable instead.
Passing by reference¶
What happens if we modify a function argument? Are the changes visible on the outside?
In Julia, the answer is "yes" (unless an objects is immutable
).
Variables are passed into functions "by reference."
Modifying a function argument changes its value after the function returns
julia> function h(x)
x[1] = 2;
println(x);
return nothing
end
h (generic function with 1 method)
julia> z = [1,2];
julia> h(z)
[2, 2]
julia> z
2-element Array{Int64,1}:
2
2
But note that this does not happen when the argument is immutable.
julia> function h2(x)
x = 19;
@show x;
return nothing
end
h2 (generic function with 1 method)
julia> z = "input"
"input"
julia> h2(z)
x = 19
julia> z
"input"
Generally, it is cleaner to avoid mutating arguments. But this can be inefficient.
When arguments are mutated, the function name should end in !
.
Example
julia> x = [3,1,4,2]
4-element Vector{Int64}:
3
1
4
2
julia> sort(x)
4-element Vector{Int64}:
1
2
3
4
julia> x
4-element Vector{Int64}:
3
1
4
2
julia> sort!(x);
julia> x
4-element Vector{Int64}:
1
2
3
4
Global Variables¶
To make a variable visible from anywhere, define it as a global
.
Remark: Avoid globals where possible. Unless they are constants.
A function should be a self-contained unit with a clear interface to the outside world (via its input and output arguments).
Globals create confusion.