Eval works in the global scope and can be tricky to use within functions. Changing your definition such that it creates a function with positional arguments gives a hint
julia> function eval_vars(expr::Expr, vars::NamedTuple)
foo = eval(Expr(:->, Expr(:tuple, keys(vars)...), expr))
@show methods(foo)
return foo(values(vars)...)
end
eval_vars (generic function with 1 method)
julia> eval_vars(ex, vs)
methods(foo) = # 1 method for anonymous function "#62":
[1] (::var"#62#63")(a, b) in Main
ERROR: MethodError: no method matching (::var"#62#63")(::Int64, ::Int64)
The applicable method may be too new: running in world age 32477, while current world is 32478.
Closest candidates are:
(::var"#62#63")(::Any, ::Any) at none:0 (method too new to be called from this world context.)
I.e., the created function cannot be called immediately, but only after your function returns:
julia> function eval_vars(expr::Expr, vars::NamedTuple)
return eval(Expr(:->, Expr(:parameters, keys(vars)...), expr))
end
eval_vars (generic function with 1 method)
julia> eval_vars(ex, vs)(; vs...)
25
The simplest way to evaluate everything within a function, is to create an expression which directly computes the desired result when evaluated, e.g.,
julia> function eval_vars(expr::Expr, vars::NamedTuple)
eval(:(let $(map((k, v) -> :($k = $v), keys(vars), values(vars))...); $expr end))
end
eval_vars (generic function with 1 method)
julia> eval_vars(ex, vs)
25