Replacing eval(Meta.parse(...)) with macro

Hello,
I want to push a number of anonymous functions to an array. Each function is largely the same but in some cases some addends are scalars instead of variables. Say that there are three different cases, like:
case 1) f(x) = x[1]+x[3]+cos(x[1]-x[2])
case 2) f(x) = x[1]+x[3]+cos(a) where a is a scalar
case 3) f(x) = x[1]+x[3]+cos(x[1]-x[3])
(in reality the function is way bigger).
So, instead of writing something like this:

function_array = []
for case in cases
    if case.type == 1
      push!(functions_array, x->x[1]+x[3]+cos(x[1]-x[2]) )
   elseif case.type ==2

etc.

I figure I’d use metaprogramming and do

function_array = []
for case in cases
   if case.type == 1
      cos_arg = :(x[1]-x[2])
   elseif case.type == 2 
     cos_arg = :(a)
   else 
     cos_arg = :(x[1]-x[3])
   end
 push!(function_array, x->x[1]+x[3]+cos(eval(Meta.parse(cos_arg))) )
end

But then I realized that eval(Meta.parse(...)) is in general a bad idea and eval only does it in the global scope, which is not good in my case.
So I guess I should write a macro instead, but it’s something I never did and I am not sure on how to best approach this. I did read the metaprogramming section of the manual but I am still a bit confused, and could use some tips.

Hmm, maybe MWE is incomplete, but it looks like you do not need metaprogramming. Functions are first-class citizens in Julia, so you can use them as lego blocks to build arbitrarily complicated expressions.

function_array = []
cases = [(; type = 1), (; type = 2), (; type = 3)]
a = 1
for case in cases
    f1 = if case.type == 1
        x -> x[1] - x[2]
    elseif case.type == 2
        x -> a
    else
        x -> x[1] + x[3]
    end
    push!(function_array, x -> x[1] + x[3] + cos(f1(x)))
end

and sanity check

julia> map(i -> function_array[i]([1, 4, 7]), 1:3)
3-element Vector{Float64}:
 7.010007503399555
 8.54030230586814
 7.854499966191386
1 Like

Thanks, that’s quite interesting and I did not know that!
However, I am afraid that I have over-simplified my problem, so in my actual case this solution does not seem to work. Let me see if I can explain my problem a bit more in detail. I am trying to write a function more like the following (again with the eval(Meta.parse(…)) approach):

function func_name(connected_branches::Array, case, function_array::Array, angle_from, angle_to)
    ev_arg = eval ∘ Meta.parse ∘ string
    if case == 1
          cos_arg = :(deg2rad.([0, 120, -120])[c] - x[angle_to[br][d]]) # angle_from and angle_to are arrays of scalars
    else
        cos_arg = :(x[angle_from[c]] - x[angle_to[br][d]])
    end
   for c in 1:3
      push!(function_array, x->sum( (x[1]+x[2]+cos(ev_arg(cos_arg)) for d in 1:3 if d!=c) for br in connected_branches) )
   end
end

so, basically, if I try to apply the method you suggested directly, I get UndefVarError: br not defined, because this is somehow “nested” in the variable index.
The inputs would look sort of like:

connected_branches = [1, 2]
case = 1
function_array = []
angle_from = [1, 2, 3]
angle_to = [[2, 4, 6], [1, 3, 5]]

I hope it is kind of clear and makes somewhat sense. Basically, angle_to and angle_from host the indexes of the variable array that I want to choose.

You can make c, d and br arguments of internal functions

function func_name(connected_branches::Array, case, function_array::Array, angle_from, angle_to)
   f1 =  if case == 1
          (x, c, d, br) -> deg2rad.([0, 120, -120])[c] - x[angle_to[br][d]] # angle_from and angle_to are arrays of scalars
    else
        (x, c, d, br) -> x[angle_from[c]] - x[angle_to[br][d]]
    end
   for c in 1:3
      push!(function_array, x->sum( (x[1]+x[2]+cos(f1(x, c, d, br)) for d in 1:3 if d!=c) for br in connected_branches) )
   end
end
1 Like

works perfectly, thank you!

1 Like