# Function that creates generated function using interpolation?

Hi, so I’d like to to create a function that allows me to automate the creation of generated functions.

Here is an example of the type of generated function I’d like to create:

``````using Reduce
expr = :(x^2)
@generated function fun(x,::Val{p}) where p
:(@fastmath \$(horner(df(expr,:x,p))))
end
``````

However, I’d like to create a function that creates this function for me, so I want to be able to do

``````genfun(:fun,expr)
``````

where `:fun` symbol will be the name of the generated function and `expr` is substituted into it.

An approximation of what I want looks like this:

``````function genfun(fun::Symbol,expr)
@eval begin
@generated function \$fun(x,::Val{p}) where p
:(@fastmath \$(horner(df(\$\$(QuoteNode(expr)),:x,p))))
end
end
end
``````

However, I’m not exactly sure how to properly interpolate the expressions for the desired result.

Does anyone know how to do something like this?

NOTE this requires the master branch of `Reduce`, so `Pkg.checkout`

1 Like

You should use a macro for that.

As I understand it, if I use a macro then the generated function will not be in the desired scope.

I need the function to be defined in my module, if I use a macro, it will not be available to call, right?

Either way, the main hurdle is the interpolation, which I would need to do whether it’s a macro or not.

Assuming that you mean `Reduce` with “my module”:
If you add a method to a function defined in your module then it will be available there too.

Then, it it not just:

``````function genfun(fun::Symbol,expr)
@eval begin
@generated function \$fun(x,::Val{p}) where p
:(@fastmath \$(horner(df(expr),:x,p))))
end
end
end
``````
1 Like

Hm… it is closer to solution, but does not work completely yet. Since I had `expr = :(x^2)` defined in REPL, it seemed to work, but if I restart Julia, then `expr` is not found:

``````function genfun(fun::Symbol,expr)
@eval begin
@generated function \$fun(x,::Val{p}) where p
:(@fastmath \$(horner(df(expr,:x,p))))
end
\$fun(x,p::Int) = \$fun(x,Val(p))
end
end
``````

example

``````julia> genfun(:f1,:((4x^4-44x^3+61x^2+270x-525)/100))
f1 (generic function with 3 methods)

julia> f1(2,0)
ERROR: UndefVarError: expr not defined
Stacktrace:
 f1(...) at ./REPL:4
 f1(::Int64, ::Int64) at ./REPL:6
 macro expansion at ./REPL.jl:97 [inlined]
 (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:73
``````

So `expr` should be inserted into the generated function when `genfun` is called, but it is not inserted.

Does nobody know how to get `expr` to insert into the generated function?

There must be a way to quote it…

Not exactly what you asked for, but this is what it looks like now that I finally got it to work:

``````function genfun(fun::Symbol,expr)
quote_expr = Expr(:quote, expr)
fun_expr_val = quote
@generated function \$fun(x,::Val{p}) where p
expr = \$quote_expr
dfexpr = df(expr,:x,p)
hdfe = horner(dfexpr)
:(@fastmath \$hdfe)
end
end
fun_expr = quote
function \$fun(x, p)
Base.Cartesian.@nif 10 i -> (i-1 == p) i -> (\$fun(x, Val{i-1}())) i -> throw("Unsupported!")
end
end
@eval begin
\$fun_expr_val
\$fun_expr
end
end
genfun(:f8,:((4x^4-44x^3+61x^2+270x-525)/100))
f8(2,0)#-0.29
f8(2,Val(0))#-0.29
f8(2,1)#1.14
``````

To more directly answer your question, quote the expr: `quote_expr = Expr(:quote, expr)`, so now you have two layers of quotes. Inserting deletes one layer, so you still have another left.

What I have above can probably be condensed. I just broke things into pieces while playing with it, to try and figure out what was going on.

EDIT:
A cool upgrade on the Base.Cartesian.@nif version would be to run inference on the return type of `\$fun` for some `Val(i)`, and then instead of throwing an error if `p>10-1` (or whatever the cutoff for number of if statements), have it do a dynamic dispatch with a type assertion thrown on so the whole thing remains type stable.
You can’t do the dynamic dispatch without an assertion and have it still be type stable.

1 Like

Here is another challenge:

Thanks for your help. You actually somewhat predicted my next goal, but I wanted something different:

``````hdf(expr,p::Int) = horner(df(expr,:x,p))
``````
``````function genfun(fun::Symbol,expr;depth=-1)
if depth < 0
@eval begin
@generated function \$fun(x,::Val{p}) where p
expr = \$(Expr(:quote,expr))
:(@fastmath \$(horner(df(expr,:x,p))))
end
\$fun(x,p::Int) = \$fun(x,Val(p))
end
else
lexpr = [Expr(:quote,hdf(expr,i)) for i ∈ 0:depth-1]
@eval begin
N = 1+\$depth
expr = \$lexpr
function \$fun(x,p)
macroexpand(quote
Base.Cartesian.@nif(\$N,
i->(i-1 == p),
i->(\$(:(expr[i]))),
i->throw(error("\$(p)-th derivative not supported")))
end)
end
end
end
end
``````

So the purpose of this function is that you can either make a generated function that evaluates derivatives up to arbitrary depth by default (with slower performance), but one should have the option of specifying a depth limit, which should then produce a plain function that uses the if-then-else structure.

``````julia> genfun(:fa,:(x^2))
fa (generic function with 2 methods)

julia> fa(2,0)
4
``````

So the generated function now works properly. However, producing the right kind of if-then-else plain function for faster performance requires the `expr[i]` derivatives to be inserted in each conditional.

In order to help debug this, the produced function outputs the if-then-else code instead of evaluating:

``````julia> genfun(:fb,:(x^2);depth=2)
fb (generic function with 1 method)

julia> fb(2,0)
quote  # REPL, line 17:
if 0 == p
expr
else
if 1 == p
expr
else
throw(error("\$(p)-th derivative not supported"))
end
end
end
``````

If someone could figure out how to insert / interpolate `expr[i]` into that, I would be very thankful.

Otherwise, instead of using `Base.Cartesian.@nif`, a new function needs to take care of it.