How to avoid capturing variables in functions?

How can I avoid creating closures? I noticed that due to the Julia syntax/REPL I inadvertently create a lot of closures. Consider my typical workflow where code evolves from a unstructured script towards a more function based interface: I have a bunch of lines that do what they are supposed to, and then I want to encapsulate the task and create a clean interface. When I place the lines in a function I repeatedly forget some variables that either should be arguments to the function or computed from the arguments. Below is a minimal example that runs and returns 5:

function fun(x)
    a + x
end

a = 2

fun(3)

I forgot to make the a an argument. This runs fine now, but it will break in an other context. I thought the compiler should warn me about this? Isn’t this a step backwards?

Can I tell the function not to capture or the variable a not to be captured?

3 Likes

All you need to to is put your functions inside a module, which will prevent them from accessing globals from the REPL. Of course, any global variables inside the module will still be accessible, but those should be much easier to avoid.

6 Likes

There is also Traceur.jl which can be used to detect issues like this.

julia> a = 7
7

julia> @trace fun(3)
β”Œ Warning: uses global variable Main.a
β”” @ REPL[2]:1
β”Œ Warning: dynamic dispatch to Main.a + x
β”” @ REPL[2]:1
β”Œ Warning: fun returns Any
β”” @ REPL[2]:1
10
5 Likes

For the record, are you suggesting

module A
function fun(x)
    a + x
end
end

a = 2

using .A: fun

fun(3)

Followup: Is there something like using .A: * to bring all functions from a module? Or export * to export everything?

1 Like

using .A should bring all exported functions into the current namespace. export requires explicit naming of exported symbols.

https://docs.julialang.org/en/v1/manual/modules/

2 Likes

Yeah, exactly. Running the code you provided results in:

julia> fun(3)
ERROR: UndefVarError: a not defined
Stacktrace:
 [1] fun(::Int64) at ./REPL[1]:3
 [2] top-level scope at none:0

which is presumably what you want in this case.

2 Likes

No, I don’t think so:

julia> module A
       b=1
       end
Main.A

julia> using .A

julia> b
ERROR: UndefVarError: b not defined

1 Like

You need to add export b to your module A for that to work.

1 Like