3
votes

I'm trying to build a function that will output an expression to be assigned to a new in-memory function. I might be misinterpreting the capability of metaprogramming but, I'm trying to build a function that generates a math series and assigns it to a function such as:

enter image description here

main.jl

function series(iter)
    S = ""
    for i in 1:iter
        a = "x^$i + "
        S = S*a
    end
    return chop(S, tail=3)
end

So, this will build the pattern and I'm temporarily working with it in the repl:

julia> a = Meta.parse(series(4))
:(x ^ 1 + x ^ 2 + x ^ 3 + x ^ 4)

julia> f =eval(Meta.parse(series(4)))
120

julia> f(x) =eval(Meta.parse(series(4)))
ERROR: cannot define function f; it already has a value

Obviously eval isn't what I'm looking for in this case but, is there another function I can use? Or, is this just not a viable way to accomplish the task in Julia?

3
Do you know about evalpoly? - Oscar Smith
Thanks, I didn't that existed. But, I was hoping to abstract it to match other patterns. - nominalize
Do you know the package Lazy.jl? github.com/MikeInnes/Lazy.jl - user2317421

3 Answers

5
votes

The actual error you get has to do nothing with metaprogramming, but with the fact that you are reassigning f, which was assigned a value before:

julia> f = 10
10

julia> f(x) = x + 1
ERROR: cannot define function f; it already has a value
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[2]:1

It just doesn't like that. Call either of those variables differently.

Now to the conceptual problem. First, what you do here is not "proper" metaprogramming in Julia: why deal with strings and parsing at all? You can work directly on expressions:

julia> function series(N)
           S = Expr(:call, :+)
           for i in 1:N
               push!(S.args, :(x ^ $i))
           end
           return S
       end
series (generic function with 1 method)

julia> series(3)
:(x ^ 1 + x ^ 2 + x ^ 3)

This makes use of the fact that + belongs to the class of expressions that are automatically collected in repeated applications.

Second, you don't call eval at the appropriate place. I assume you meant to say "give me the function of x, with the body being what series(4) returns". Now, while the following works:

julia> f3(x) = eval(series(4))
f3 (generic function with 1 method)

julia> f3(2)
30

it is not ideal, as you newly compile the body every time the function is called. If you do something like that, it is preferred to expand the code once into the body at function definition:

julia> @eval f2(x) = $(series(4))
f2 (generic function with 1 method)

julia> f2(2)
30

You just need to be careful with hygiene here. All depends on the fact that you know that the generated body is formulated in terms of x, and the function argument matches that. In my opinion, the most Julian way of implementing your idea is through a macro:

julia> macro series(N::Int, x)
           S = Expr(:call, :+)
           for i in 1:N
               push!(S.args, :($x ^ $i))
           end
           return S
       end
@series (macro with 1 method)

julia> @macroexpand @series(4, 2)
:(2 ^ 1 + 2 ^ 2 + 2 ^ 3 + 2 ^ 4)

julia> @series(4, 2)
30

No free variables remaining in the output.

Finally, as has been noted in the comments, there's a function (and corresponding macro) evalpoly in Base which generalizes your use case. Note that this function does not use code generation -- it uses a well-designed generated function, which in combination with the optimizations results in code that is usually equal to the macro-generated code.

3
votes

Another elegant option would be to use the multiple-dispatch mechanism of Julia and dispatch the generated code on type rather than value.

@generated function series2(p::Val{N}, x) where N
    S = Expr(:call, :+)
    for i in 1:N
        push!(S.args, :(x ^ $i))
    end
    return S
end

Usage

julia> series2(Val(20), 150.5)
3.5778761722367333e43


julia> series2(Val{20}(), 150.5)
3.5778761722367333e43
0
votes

This task can be accomplished with comprehensions. I need to RTFM...

https://docs.julialang.org/en/v1/manual/arrays/#Generator-Expressions