Create a Vector with values of variables defined by list of Symbols

Hey fellow Julians,

I have a vector with symbols:

sym_list = [:(p.a), :(p.b), :(p.c)]

Given this sym_list, I’d like to automatically generate a function that plugs the values denoted by the symbols ( where p could be a NamedTuple or struct, for example) into a vector:

function flatten(p)
    return [p.a, p.b, p.c]
end

I’m stuck at synthesizing the inner part of the vector brackets:

function gen_flatten(sym_list)
    flatten_sym = gensym("flatten")
    func = quote
        function $flatten_sym(p)
            return [
                # What to put here?
            ]
        end
    end
    return eval(func)
end

I’ve tried splatting sym_list (i.e., put $(sym_list...) instead of the comment above) since that also works for calling functions. However, it didn’t work in this case and produced a syntax error:

syntax: invalid syntax "p.(Core._expr(:vect, sym_list...))"

How can I fix gen_flatten() such that it generates flatten()?

You could do this:

julia> p = (a=1, b=2, c=3)
(a = 1, b = 2, c = 3)

julia> sym_list = [:a, :b, :c];

julia> function return_vec(p,sym_list)
           map(s -> getfield(p, s), sym_list)
       end
return_vec (generic function with 1 method)

julia> return_vec(p, sym_list)
3-element Vector{Int64}:
 1
 2
 3

But are you sure you need to do that? What are you trying to do exactly?

Thanks for the suggestion. Unfortunately, it doesn’t fit my needs since no function is generated.

I am parsing a domain specific language and need to generate some code in order to use a Julia library / framework. Instead of creating some .jl file, I thought: Why not directly generate and eval() the Expressions?

You can return a function:

julia> function return_vec(p)
           return sym_list -> map(s -> getfield(p, s), sym_list)
       end
return_vec (generic function with 2 methods)

julia> return_vec(p)(sym_list)
3-element Vector{Int64}:
 1
 2
 3

That would be a working solution, I guess.

Still, I’d prefer to generate a “hardcoded” function (as in the flatten() example). However, I’ll use your approach in the meantime.

I figured it out by reverse-engineering the AST of flatten():

function gen_flatten(sym_list)
    flatten_sym = gensym("flatten")
    func = Expr(:function,
                Expr(:call, flatten_sym, :p),
                Expr(:block,
                    Expr(:return, Expr(:vect, sym_list...))
                )
            )
    return eval(func)
end

Example:

sym_list = [:(p.a), :(p.b), :(p.c)]
flatten = gen_flatten(sym_list)
flatten((a = 1, b = 2, c = 3))

For readability, I’d still be interested to see how to formulate it in the style of the original post instead of directly writing down the AST.

Another option that produces a similar result, without meta-programming, is to use a callable struct:

julia> struct Flatten{L}
           sym_list::L
       end

julia> (f::Flatten)(p) = map(s -> getfield(p, s), f.sym_list)

julia> flatten = Flatten([:a, :b, :c])
Flatten{Vector{Symbol}}([:a, :b, :c])

julia> p = (a=1, b=2, c=3)
(a = 1, b = 2, c = 3)

julia> flatten(p)
3-element Vector{Int64}:
 1
 2
 3