Actually, I have just figured out to do this macro without Base.invokelatest
and updated SyntaxTree
:
julia> using SyntaxTree, BenchmarkTools
julia> g = genfun(:(-(cos(x))),[:x])
(::#3) (generic function with 1 method)
julia> @btime $g(1.0)
40.790 ns (0 allocations: 0 bytes)
-0.5403023058681398
julia> f = @genfun -cos(x) [x]
(::#3) (generic function with 1 method)
julia> @btime $f(1.0)
40.801 ns (0 allocations: 0 bytes)
-0.5403023058681398
Now the evaluation is much faster because it is not using Base.invokelatest
anymore. The implementation
macro genfun(expr,args)
:($(Expr(:tuple,args.args...))->$expr)
end
genfun(expr,args) = :(@genfun $expr [$(args...)]) |> eval
is very simple to write in Julia in the end, it is now part of the SyntaxTree package.
1 Like
I’d be more convinced if you gave an example of non-use of invokelatest
in local scope. We already know that it’s not needed in global scope.
julia> using SyntaxTree
julia> function f(e, t)
g = genfun(e, [:x])
return g(t)
end
f (generic function with 1 method)
julia> f(:(-cos(x)), 1)
ERROR: MethodError: no method matching (::SyntaxTree.##1#2)(::Int64)
The applicable method may be too new: running in world age 21835, while current world is 21836.
Closest candidates are:
#1(::Any) at /home/gunnar/.julia/v0.6/SyntaxTree/src/SyntaxTree.jl:121 (method too new to be called from this world context.)
Stacktrace:
[1] f(::Expr, ::Int64) at ./REPL[2]:3
Indeed, then it’s best to stick to the original method requiring Base.invokelatest
and the use of if
statements to avoid the use of eval
in the anonymous function:
function genfun(expr,args::Array,gs=gensym())
eval(Expr(:function,Expr(:call,gs,args...),expr))
if length(args) == 0
()->Base.invokelatest(eval(gs))
elseif length(args) == 1
(a)->Base.invokelatest(eval(gs),a)
elseif length(args) == 2
(a,b)->Base.invokelatest(eval(gs),a,b)
elseif length(args) == 3
(a,b,c)->Base.invokelatest(eval(gs),a,b,c)
...
end
end
Okay, so the macro
variant of the genfun
function can take an arbitrary number of args
, while the function variant has to be limited to a finite number by the if
statements
macro genfun(expr,args,gs = gensym())
eval(Expr(:function,Expr(:call,gs,args.args...),expr))
:($(Expr(:tuple,args.args...))->Base.invokelatest($gs,$(args.args...)))
end