Redefining a function on the fly


#1

Is it possible to redefine a running function on the fly in Julia?
For example given the two following functions:

function omy(i)
sleep(100)
println(“hello”)
end

function test()
for i=1:200
omy(i)
end
end

If I compile both definitoons and then execute the following steps:

  • Execute on the repl: test()
  • Change the definition of omy while test is still running
  • Reevaluate the new definition of omy() while test is still running

Will I be able to see the see the effects of the newly defined omy in the running loop inside test()?


#2

Interesting question. The closest I could get was:

fref = Ref{Function}()
fref[] = ()->begin
    println("foo")
    sleep(1)
end

function outer()
    for i in 1:20
        fref[]()
        println(i)
    end
end

# run this, then redefine the function in `fref`
t = @async outer()

# run this if you want to see errors thrown by the task
wait(t)

But when you try to redefine the function you get a world-age error because outer is older than the function in fref. Generally in Julia when a func1 calls func2, then func2 gets redefined, Julia will recompile func1 so that it’s up to date. The world age stuff is the mechanism to make sure that those dependencies are updated as needed.


#3

It would be more idiomatic to make the function behavior conditional on something that you can change — can be a parameter, a shared variable (if you are using threads), or something you read from a file. The implementation of how you propose to do

is crucial. Also, perhaps you could give more context: what is it you are trying to do?

Finally, please quote your code.


#4

One way around it is to use Base.invokelatest to call the newest definition.

help?> Base.invokelatest
  invokelatest(f, args...; kwargs...)

  Calls f(args...; kwargs...), but guarantees that the most recent method of f
  will be executed. This is useful in specialized circumstances, e.g.
  long-running event loops or callback functions that may call obsolete
  versions of a function f. (The drawback is that invokelatest is somewhat
  slower than calling f directly, and the type of the result cannot be
  inferred by the compiler.)

The performance of the function call is a bit slower though.


#5

Ah yes, good idea. So the full working example looks like:

function inner()
    println("foo")
    sleep(1)
end

function outer()
    for i in 1:20
        Base.invokelatest(inner)
        println(i)
    end
end

t = @async outer()

# call this to get any errors thrown
wait(t)

#6

Hi Tamas,
my question didn’t come from nowhere and had the specific purpose of comparing the level of interactivity provided by Julia’s REPL with respect to what is provided by other “lisp languages”.

The “omy” scenario is highly typical of Common Lisp and it’s also supported by some scheme implementations (i.e. Gambit).
The best answers were by Chakravala and Ssfrr. Given that both of them were able to jump straight to the point and address the matter about late binding I believe that my question had more than enough context at least for them.
Thanks,
Regards,
Luca


#7

Have just tried it both in Atom and in a bare REPL. It doesn’t seem to work. I am changing “foo” to “hello” and evaluating again inner without any apparent effect.


#8

Thanks both, this is exactly what I was looking for.


#9

From your tone it is my impression that you may think I asked for something unreasonable.

Note however that @chakravala’s answer also points out that you will pay a performance penalty for invokelatest, as it does not mesh well with Julia’s performance model.

To give a CL example, this is a bit comparable to using cl:eval heavily: while technically you can, it is not idiomatic and you usually run into issues.

This can be easily illustrated by modifying the example above to use the result of inner, eg look at

inner(i) = (sleep(1); i)
outer(n) = sum([Base.invokelatest(inner, i) for i in 1:n])
@code_warntype outer(3)

The highlighted Anys you see in the output are a sign of type inference problems.


#10

@Tamas_Papp gave the most useful answer IMO. While it’s possible to do what you’re asking, it sounds like a thoroughly bad idea (at least in Julia), that is destined for code that’s hard to understand, bugs, and problems down the road.


#11

Another option is to annotate the type assertions, such as Base.invokelatest(...)::T when T is known