# How do I create a function from an expression

Suppose I have

``````expr = :(sin(x)+cos(x))
``````

How can I get a function looking like

``````x->sin(x)+cos(x)
``````

I have tried to write such a macro

``````macro expr2fn(expr, arg)
return :(\$arg -> \$(expr))
end
``````

but it does not work

``````julia> f = @expr2fn expr x
(::#63) (generic function with 1 method)

julia> f(1.0)
:(sin(x) + cos(x))
``````

Not sure what is happening here.

1 Like

You can call your macro as `f = @expr2fn sin(x) + cos(x) x`.

Taking the view of macros as functions that map expressions to expressions (https://docs.julialang.org/en/latest/manual/metaprogramming/#Hold-up:-why-macros?-1), with `@expr2fn expr x` youâ€™re basically passing the arguments `:(expr)` and `:(x)` to a function with a signature `expr2fn(::Expr, ::Expr)::Expr`; thatâ€™s not what you want.

Instead you could do `f = eval(:(x -> \$expr))`.

1. `expr=:(sin(x)+cos(x))` is not constructed by hand. Itâ€™s returned symbolic calculation.
2. I donâ€™t want `eval` since `eval` always acts on global scope, which limits the possibility of using it inside a function.

Could you post a concrete example where the `eval` construction doesnâ€™t do what you want?

``````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:

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