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.
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.
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
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
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.