Unpack NamedTuple into a function definition

This is an interesting question! I’ll point out some suspected confusion: when metaprogramming in Julia, code is not represented with strings, but rather expression trees. If you interpolate a string into @eval, you’ll get a literal string:

julia> e = "2 + 2"
"2 + 2"

julia> :( a = $e ) # this is what `@eval a = $e` would execute
:(a = "2 + 2")

julia> e = :(2 + 2) # instead, deal with expressions
:(2 + 2)

julia> :( a = $e )
:(a = 2 + 2)

You’d never represent code as a string unless it comes to you that way — in which case you can Meta.parse it into an expression.


On to the question: you have a named tuple which you want to use as default keyword arguments for some method definitions:

const PARAMETERS = (; a = 1, b = 2)

function query(; a = 1, b = 2) # you don’t want to repeat yourself here
  a + b
end

One way to do this is like so:

const PARAMETERS = (; a = 1, b = 2)

kws = [Expr(:kw, k, v) for (k, v) in pairs(PARAMETERS)]

@eval function query(; $(kws...))
    a + b
end

You can see the unevaluated code by replacing @eval with quote … end. This requires a little knowledge of how keyword arguments are parsed internally, so I’ll explain how I got there.

If you inspect the structure of an expression with keyword arguments, you’ll see they’re actually Expr(:kw, k, v) objects.

julia> :( f(; a = 1) ) |> dump
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol f
    2: Expr
      head: Symbol parameters
      args: Array{Any}((1,))
        1: Expr
          head: Symbol kw
          args: Array{Any}((2,))
            1: Symbol a
            2: Int64 1

julia> :( f(; a = 1, b = 2) ).args[2].args # these are the `a = 1, b = 2`
2-element Vector{Any}:
 :($(Expr(:kw, :a, 1)))
 :($(Expr(:kw, :b, 2)))

To properly interpolate (; a = 1, b = 2) into an expression as keyword arguments, we have to convert them into a collection of Expr(:kw, k, v) structs. That is what the kws line above does. Finally, the dot syntax in f(; $(kws...)) is equivalent to f(; $(kws[1]), $(kws[2]), …, $(kws[end])). Hope this helps.

4 Likes