How do I create a function from an expression

Copied from Generate functions inside a function from string

function foo()
    return eval(parse("x->x"))
end
foo()(1)  
# ERROR: MethodError: no method matching (::##9#10)(::Int64)
# The applicable method may be too new: 
# running in world age 21870, while current world is 21871.

But I think I found a fix

macro expr2fn(fname, expr, args...)
    fn = quote
        function $(esc(fname))()
            $(esc(expr.args[1]))
        end
    end
    for arg in args
        push!(fn.args[2].args[1].args, esc(arg))
    end
    return fn
end

@expr2fn(f, :(sin(x+y)), x, y)

julia> f(1.0, 1.0)
0.9092974268256817

I guess it’s just the documentation is never clear about what I can get if I pass a quoted expression into a macro.

FWIW, eval works fine on 0.7:

   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: https://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.7.0-beta.201 (2018-07-07 22:13 UTC)
 _/ |\__'_|_|_|\__'_|  |  Commit cdd4e84ac9 (5 days old master)
|__/                   |  x86_64-apple-darwin14.5.0

julia> function foo()
           return eval(Meta.parse("x->x"))
       end
foo (generic function with 1 method)

julia> foo()(1)
1
1 Like

Also, assigning the function to a variable circumvents the world age issue on 0.6:

julia> function foo()
           return eval(parse("x->x"))
       end
foo (generic function with 1 method)

julia> f = foo()
(::#3) (generic function with 1 method)

julia> f(1)
1

That helps you at the REPL but not so much in a function.

julia> function g()
           f = foo()
           println(f(1))
       end
g (generic function with 1 method)

julia> g()
ERROR: MethodError: no method matching (::##1#2)(::Int64)
The applicable method may be too new: running in world age 21838, while current world is 21839.
Closest candidates are:
  #1(::Any) at none:1 (method too new to be called from this world context.)
Stacktrace:
 [1] g() at ./REPL[13]:3

Yep, you’re right. And that one doesn’t work on 0.7 either.

This only works because you’re sending in the literal :(sin(x+y)) to your macro.

julia> expr = :(sin(x+y))
:(sin(x + y))

julia> @expr2fn(f, expr, x, y)
ERROR: type Symbol has no field args

Fundamentally macros work on syntax, not on values, and since you want to be able to generate functions from values, macros are not the appropriate tool. As far as I know only eval in some form can get you there.

Yeah, I think invokelatest is the only way out then if you really want this to work in local scope. I’ve personally never needed anything like this by the way; maybe you could talk a little about your intended application?

This may be illustrative of the problem and two possible ways to circumvent the world age issue.

julia> expr = :(sin(x + y))
:(sin(x + y))

julia> function f1(expr, x, y)
           f = eval(:((x, y) -> $expr))
           return f(x, y)
       end
f1 (generic function with 1 method)

julia> function f2(expr, x, y)
           f = eval(:((x, y) -> $expr))
           return @eval $f($x, $y)
       end
f2 (generic function with 1 method)

julia> function f3(expr, x, y)
           f = eval(:((x, y) -> $expr))
           return Base.invokelatest(f, x, y)
       end
f3 (generic function with 1 method)

julia> f1(expr, 1, 2)
ERROR: MethodError: no method matching (::##1#2)(::Int64, ::Int64)
The applicable method may be too new: running in world age 21836, while current world is 21837.
Closest candidates are:
  #1(::Any, ::Any) at REPL[2]:2 (method too new to be called from this world context.)
Stacktrace:
 [1] f1(::Expr, ::Int64, ::Int64) at ./REPL[2]:3

julia> f2(expr, 1, 2)
0.1411200080598672

julia> f3(expr, 1, 2)
0.1411200080598672

Thank you for all the comments. What do you think of my following implementation?

module Expr2Fn

export @expr2fn, setexpr

expr = :(sin(x+y))

macro expr2fn(fname, args...)
    fn = quote
        function $(esc(fname))()
            $expr
        end
    end
    for arg in args
        push!(fn.args[2].args[1].args, esc(arg))
    end
    return fn
end

function setexpr(extexpr)
    global expr = extexpr
end

end

using Expr2Fn

setexpr(:(cos(x+y)))

@expr2fn(f, x, y)

julia> f(1.0, 1.0)
-0.4161468365471424

A little bit abuse of Hygiene but seems to work.

Please see the post for intention (I am her colleague):

I am starting a new thread because I think eval is not the way out since it’s too restrictive to support local scope.

I think you will find, when you try to run it in a local scope, that it’s less local than you hope and/or runs into world age issues.

2 Likes

You can make that macro statement a lot simpler like this

macro expr2fn(expr, fname, args...)
    return Expr(:function, Expr(:call, esc(fname), args...), expr)
end

although unfortunately the function that is defined by a macro like this is not accessible in the calling scope.

Not sure if this is the “right” way to do it, but it works:

instead of
julia> f = @expr2fn expr x

try:

julia> expr2 = :(f = @expr2fn $expr x)
:(f = @expr2fn(sin(x) + cos(x), x))

julia> eval(expr2)
(::#21) (generic function with 1 method)

julia> f(1.0)
1.3817732906760363

Can I back this up a level and ask what you are trying to accomplish? I feel like this may be a XY problem situation.

2 Likes

That’s impossible.

No, it’s the global scope that does.

If the input is not static (if it’s static you should/could use a macro to do the symbolic calculation) then if you want to create the function you must use eval. After you’ve done with all the function/expression construction, you can use eval or invokelatest to call the code that uses those functions.

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.

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