Help calling a function defined from expressions

This is similar to the issue discussed in callback functions see "old world" in 0.6 · Issue #19774 · JuliaLang/julia · GitHub, and has to do with the resolution to #265.

In Julia 0.6, when you change the definition of a function foo, any functions that call foo are recompiled the next time they are called. Technically, this involves “timestamping” each method with a “world age” that says how old it is, which is used to determine whether it needs to be recompiled. Moreover, when you are executing a function, it only calls other functions from the “world” as it existed when the function was compiled.

This affects you if you call eval during execution of a function foo. Any new methods that are defined by the eval are in a newer world, and hence are not the functions that are called within the same call to foo. (The next call to foo will recompile it and see the newer world.) To see why that is, consider:

bar() = 1
function foo()
    eval(current_module(), :(bar() = "hello"))
    return bar()
end

What will foo() return? The first time you call it, it will return 1, because it will use the old version of bar() that was defined when foo() was first compiled. The second time you call it, it will return "hello", because it will be automatically recompiled (since it depends on bar and bar has changed). In fact, the function bar() will be inlined when foo() is compiled, so there is no way for foo() to get the “updated” version of bar() without recompiling foo(), which can’t happen while foo() is running — it has to wait until the next call.

Now, suppose you don’t like this behavior: you want foo() to return "hello" the first time you call it, i.e. you want it to always call the latest version of bar(). To achieve that, not only must the compiler not inline the function bar(), it can’t infer the return type of bar() either, because you might redefine bar() to return a different type (as we did above) — the function call would have to be completely dynamic. If the compiler did that for every function you call in any function, it would completely kill performance. By default, therefore, it has to call functions as they were defined when foo() was compiled. However, you can invoke the latest version “manually” by calling eval:

bar() = 1
function foo()
    eval(current_module(), :(bar() = "hello"))
    return eval(current_module(), :(bar()))
end

at the price of performance (no inlining or type inference). (Calling eval is awkward enough, in the rare cases where this is needed, that I’d prefer to have an invokelatest intrinsic function for this: add invokelatest to circumvent world-age problems by stevengj · Pull Request #19784 · JuliaLang/julia · GitHub)

A subtle design choice arises for Function objects that are not known at compile time, such as a new function that you construct via eval (as in your example) or a Function extracted from a pointer in a callback routine (as in the issue I linked). In this case, the compiler won’t be able to inline it or (probably) do type-inference on the result, so it might as well invoke the latest-world version of the function, no? On the other hand, this makes the semantics dependent on type inference (if it can infer the specific type of function, it invokes the old-world version, but if it can only infer Function, then it invokes the new-world version), and it is hard to define precisely when inference will succeed.

My preference would still be to call the latest-world version of a function when its type is inferred as Function, since I think this is almost always what you want (especially for anonymous functions that aren’t even defined in the old world), and world-age errors are awfully confusing, but reasonable people can disagree about this.

16 Likes