How do I create a function from an expression

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