Scoping fun in Julia
Background
Julia is a high-level programming language that is exceptionally well suited for scientific and mathematical applications. If you are not familiar with it, you should give it a try!
Last year, Julia 1.0 was finally released. Among other important changes, it introduced a different set of scoping rules. For the unwary, these can lead to quite unexpected behavior, and in some cases subtle but embarassing errors (I am speaking out of personal experience here!). What follows is a little example that shows what to watch out for.
Example
Let’s assume we write a function that iteratively updates the state of a system, e.g., given in the form of an array that goes by the name of ‘x’:
function update(x0,eps0)
x = copy(x0)
while true
# x1 = next_state(x)
x1 = sqrt(x)
if maximum(abs(x1 - x)) < eps0
break
else
x = x1
end
end
x1
end
What will happen when we run this code?
The return value ‘x1’ on the last line will not be defined, since the variable ‘x1’ is created inside the loop and then goes out of scope after the loop is finished. So this example will return an error:
julia> update(2.0, 1e-6)
ERROR: UndefVarError: x1 not defined
Unfortunately, Julia also supports closures. So if we define ‘x1’ globally before calling ‘update’, the error will be masked:
julia> x1 = 0.0
julia> update(2.0, 1e-6)
0.0
julia>
Related to this is the issue that loop variables will go out of scope after the loop. In other words, you cannot return something through variables created in the loop!
julia> for z = 1:10
end
julia> println(z)
ERROR: UndefVarError: z not defined
julia> z = -1
-1
julia> for z = 1:10
end
julia> println(z)
-1.0
But what about the following:
julia> z = -1
-1
julia> for y = 1:10
z = y
end
julia> z
-1
Now this is a change made consciously for Julia 1.0 that can lead to some frustration. At the REPL, only variables declared as /global/ variables will be accessible inside scoping blocks! So you need to write:
julia> global z = -1
-1
julia> for y = 1:10
global z = y
end
julia> z
10
The reason is, of course, that global variables should be avoided as much as possible. Not only do they pollute the global namespace, but they do not allow the Julia compiler to optimize code for specific variable types. This leads to hugely inefficient code. So it makes sense to make the use of globals highly restrictive.
However, this is inconvenient for scripts, right?
Recommendation
Just make sure to keep in mind the following two things:
- Loop-variables cannot be returned directly
- Initialize all variables used in a function before first use!
function update(x0,eps0)
x = copy(x0)
x1 = 0.0
while true
# x1 = next_state(x)
x1 = sqrt(x)
if maximum(abs(x1 - x)) < eps0
break
else
x = x1
end
end
x1
end