Is there a way to create task from not yet defined function without eval?

I have a code like this

jobs = []

function main()
    while true
        for j in jobs
            @async j()

# f() = println("f") # this works
@async main()
f() = println("f") # doesn't work

push!(jobs, f)

The problem is the following. If I define a function f and add it to the jobs collection after starting @async main() then it does nothing (it works if f is defined before). I can fix it by using eval around @async j() but I’m wondering is there a better approach? I’ve tried defining a macro, but with no success. Also, what’s the reason for this? I thought that task is like a function that accepts function as input.

Can you give a full MWE where you’re defining the jobs too? Hard to diagnose the problem without the whole context.

Edited the question for clarity

If you modify your main like so:

You’ll get this output when you push!(jobs, f):

julia> Task (failed) @0x000076edaa7c95f0
julia> MethodError: no method matching f()
The function `f` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  f() (method too new to be called from this world context.)
   @ Main REPL[4]:1

 [1] (::var"#3#4"{typeof(f)})()
   @ Main ./REPL[2]:5
Task (failed) @0x000076eda78007e0

Note the “method too new to be called from this world context” bit. This is because methods can only call other methods that are “at least as old” as themselves. You can use invokelatest to get around this, though in general it’s better (for precompilability, type stability etc) to design your program in such a way that you don’t rely on being able to call methods defined after you start your program.


invokelatest works and I understand that it’s better to define methods before calling them, in general. I thought that async execution of main could take into account the changing “world order”.

At the most fundamental level, it can’t! The way eval (which is what happens when a new method/object is defined) is limited in Julia is precisely what allows it to be compiled to native code ahead of time, instead of having to insert dynamic lookups every time you call a function, in spite of being a semantically dynamic language.

1 Like


1 Like