Writing my own callbacks

I thought I finally understood the various subtleties of scope and closure in Julia, but obviously not! Here is a self-contained MWE that demonstrates a callback that does not work and I do not know how to fix:

iter = 8

function callback(iter)
    println("callback: iter= ", iter)
end

cb2() = callback(iter)

function solver(; cb=callback)
    callback()
end

solver(; cb=cb2)

iter is a variable at global scope. I get the error message when executing the last line:

ERROR: UndefVarError: iter not defined
Stacktrace:
 [1] callback()
   @ Main ~/src/2022/rude/giesekus/test_global.jl:30
 [2] solver(; cb::Function)
   @ Main ~/src/2022/rude/giesekus/test_global.jl:40
 [3] top-level scope
   @ ~/src/2022/rude/giesekus/test_global.jl:44

which seems rather straightforward.

I have defined the closure cb2() = callback(iter), which I thought would capture the variable iter. When the callback is called from within solver, I expected iter inside callback to be visible, and yet it wasn’t. What is happening? Any insight is appreciated. Thanks.

I think you want to call cb() here? With this change, I get:

julia> solver(; cb=cb2)
callback: iter= 8
4 Likes

Thank you! I just figured it out, returned to discourse, and you beat me to it! Thank you.

cb2 is not a closure over iter. At least not in the sense I understand the term. It merely references Main.iter when called; it doesn’t capture it on declaration.

julia> cb()
callback: iter= 8

julia> iter = 7
7

julia> cb()
callback: iter= 7

In constrast, this will produce a closure.

julia> function makecb(iter)
         () -> callback(iter)
       end
makecb (generic function with 1 method)

julia> cb_closure = makecb(iter)
#3 (generic function with 1 method)

julia> cb_closure()
callback: iter= 7

julia> iter = 6
6

julia> cb_closure()
callback: iter= 7

Observe that cb_closure has a field iter in contrast to cb2.

julia> hasproperty(cb_closure, :iter)
true

julia> cb_closure.iter
7
2 Likes

Note that your original code doesn’t produce the error you reported, but “ERROR: MethodError: no method matching callback()” because of course the callback function is defined only with one argument, there isn’t any method with zero arguments…

Noted. Thanks for the addendum.