Pass expression to manipulate local variable

Thanks! It works when using invokelatest.
Why is it actually needed in this case? What isn’t “latest” when I create functions in the loop?

How will it behave in a multi-threaded environment when the code inside the loop is done by different workers? Will they have a collision?

eval changes world age, which is needed to keep coherence when redefining functions which are already referenced by other functions.

1 Like

I think I’ve finally got it, thanks to everyone in this thread for the help!!! :smiley:

Here’s the code after it evolved (hehe) through this thread:

mutable struct state
    x::Int64
end

generate_candidate_func(hook) = eval(
    quote
        state -> begin
            for t in 1:1000
                $hook
            end
        end
    end
)

function eval_candidate_func(hook::Expr, invoke_times=1)
    candidate_func = generate_candidate_func(hook)
    
    sx_max = 0
    for i in 1:invoke_times
        s = state(0)
        Base.invokelatest(candidate_func, s) # mutates s
        sx_max = max(s.x, sx_max)
    end

    return sx_max
end

function search_candidate_func(n_iter)
    desired_score = 50
    best_score = Inf
    for i in 1:n_iter
        hook = :(state.x = $i)
        sx = eval_candidate_func(hook)
        score = min(best_score, abs(desired_score - s.x))
    end
    best_score
end

The compilation takes short time, and the execution of the compiled function takes negligible time:

myhook = :(state.x = 50)
eval_candidate_func(myhook, 1)  # warm up hook before benchmark

@btime eval_candidate_func($myhook, 1)
#  3.202 ms (17874 allocations: 1.08 MiB)

@btime eval_candidate_func($myhook, 100)
#  3.220 ms (17973 allocations: 1.08 MiB)

@btime eval_candidate_func($myhook, 1000)
#  3.387 ms (18873 allocations: 1.09 MiB)

So to answer directly to my simple example from the original question

A working solution for my wrongly phrased question would be:

function ex_v(v_init::Integer, hook::Expr)
    eval(quote
        v = $v_init
        $hook
        return v
    end)
end

@assert ex_v(0, :(v = v+1)) == 1

But the solution which I actually needed would be:

function ex_v_func(hook::Expr)
    eval(
        quote
            v_init::Integer -> begin
                v = v_init
                $hook
                return v
            end
        end
    )
end

ex_v = ex_v_func(:(v = v+1))
@assert ex_v(0)==1
1 Like