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?

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.

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

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?

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/

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.

No, I don’t think so:

julia> module A
       b=1
       end
Main.A

julia> using .A

julia> b
ERROR: UndefVarError: b not defined

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