Hi - I’m currently reading the manual to learn Julia.
I’ve spotted maybe an incomplete example in the Methods chapter on redefining methods.
It seems to say that there are two definitions of the newfun() function, but I can only spot what must be the “new” definition within the tryeval() function body.
Could someone maybe confirm this or explain why I might be wrong/misunderstanding?
The definition of newfun is not a closure inside tryeval, it’s evaluated into the global scope. That’s true for all @eval code. The old (or nonexistent) versus new definitions are split across different world ages. You could study world age in that section and the linked page, but in practice, you’ll want to stick to closures or wait until function calls like tryeval() return all the way to the global scope before calling any of the methods.
Many thanks for your reply. Given what you’ve said, I’ve read The World Age mechanism documentation page (though it seems the link at the bottom of the screenshot (in the “Redefining Methods” section) is not pointing to the right webpage…), and I think I understand.
I trialled a few things in the REPL, and want to run a few things by you if possible. First, here’s what I tested:
julia> function tryeval()
newfun() = 1
return newfun()
end
tryeval (generic function with 1 method)
julia> tryeval()
1
julia> newfun()
ERROR: UndefVarError: `newfun` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
[1] top-level scope
@ REPL[5]:1
julia> function tryeval2()
@eval newfun2() = 1
return newfun2()
end
tryeval2 (generic function with 1 method)
julia> tryeval2()
WARNING: Detected access to binding `Main.newfun2` in a world prior to its definition world.
Julia 1.12 has introduced more strict world age semantics for global bindings.
!!! This code may malfunction under Revise.
!!! This code will error in future versions of Julia.
Hint: Add an appropriate `invokelatest` around the access to this binding.
To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`.
ERROR: MethodError: no method matching newfun2()
The applicable method may be too new: running in world age 38703, while current world is 38705.
Closest candidates are:
newfun2() (method too new to be called from this world context.)
@ Main REPL[6]:2
Stacktrace:
[1] tryeval2()
@ Main ./REPL[6]:3
[2] top-level scope
@ REPL[7]:1
julia> newfun2()
1
Therefore:
If tryeval() above is run, there is no problem, because newfun() is just a new function/method defined in the local scope of tryeval(). Because of this, it is defined within the same world age as in the definition of tryeval() - and so when tryeval() is called, the evaluation of newfun() is within the same world age?
since the @eval macro evaluated things in a global scope (?) as you say, the definition/evaluation of newfun2() occurs in the global (newest) world age, yet the definition of tryeval2() occurs within a previous world age. Therefore, only because of the special behaviour of @eval, this issue occurs?
I also found this example in the world age mechanism section:
julia> function f end
f (generic function with 0 methods)
julia> begin
@show (Int(Base.get_world_counter()), Int(Base.tls_world_age()))
Core.eval(@__MODULE__, :(f() = 1))
@show (Int(Base.get_world_counter()), Int(Base.tls_world_age()))
f()
end
(Int(Base.get_world_counter()), Int(Base.tls_world_age())) = (38452, 38452)
(Int(Base.get_world_counter()), Int(Base.tls_world_age())) = (38453, 38452)
ERROR: MethodError: no method matching f()
The applicable method may be too new: running in current world age 38452, while global world is 38453.
Closest candidates are:
f() (method too new to be called from this world context.)
@ Main REPL[2]:3
Stacktrace:
[1] top-level scope
@ REPL[2]:5
julia> (f(), Int(Base.tls_world_age()))
(1, 38453)
Correct me if I’m wrong, but is this example in essence the same as in the “Redefining Methods” section (just with a slightly different @eval calling)? If so, I find it very helpful to actually have a visible empty definition of the function in question ("function f end "). I feel like this could be a lot clearer in the redefining methods section.
Final Question, is a closure just a function that references variables outside the hard local scope of that function? Is this the only difference between a standard “function” and a “closure”?
Yes, but also note that running tryeval does not create new worlds (because newfun is local, as you said).
Defining a new method causes a world age increment, but tryeval2runs in a fixed world. You can work around that with e.g. invokelatest or Base.invoke_in_world, although that is not recommended for non-interactive uses. @eval isn’t really what causes the issue, it’s just a convenient way to create methods in newer worlds (and also a common anti-pattern)
Yup.
Technically yes, but “closure” is most often used for local functions referencing variables in an outer local scope. The main point @Benny was trying to make, I believe, is that local function definitions in your method body are much preferred over trying to modify global state.