Unpack named tuples (does anyone know how to do it?)

I started thinking about this recently after seeing this thread. I mostly agree with @Tamas_Papp that a struct is likely the best solution, however I think these sorts of code transformations are interesting to think about.

Basically, what would be needed to do this is a macro that knows about type information. Base Julia does not have such a construct, but IRTools.jl sorta does with it’s @dynamo.

Here is a dynamo I call with that in some senses will behave like @unpack, even on a named Tuple:

using IRTools: IRTools, @dynamo, @code_ir, postwalk,  prewalk, IR, argument!, 
using IRTools: xcall, var, insertafter!, isexpr, branches, blocks
using Setfield: @set

@dynamo function with(f, obj)
    ir = IR(f)
    objarg = argument!(ir)
    d = Dict()
    for fn ∈ reverse(fieldnames(obj))
        d[fn] = insertafter!(ir, var(1), xcall(:getfield, objarg, QuoteNode(fn)))
    end

    function replacer(x)
        if x isa GlobalRef && x.name ∈ fieldnames(obj)
            d[x.name]
        else
            x
        end
    end

    for (n, st) ∈ ir
        ir[n] = prewalk(replacer, st)
    end

    for blck ∈ blocks(ir)
        bs = branches(blck)
        for i ∈ eachindex(bs)
            b = bs[i]
            bs[i] = @set b.condition = postwalk(replacer, b.condition)
            for j ∈ eachindex(bs[i].args) 
                bs[i].args[j] = postwalk(replacer, bs[i].args[j])
            end
        end
        ir
    end
    ir
end

Apologies for the spaghetti code, I’m not yet very skilled with writing IR. Here’s with in action:

julia> nt = (;a=1, b=4.0, c="hi", d=true)
(a = 1, b = 4.0, c = "hi", d = true)

julia> with(nt) do
           if !d
               (a - b)/(a + b)
           else
               c
           end
       end
"hi"

This is a rather brittle solution that has many weaknesses.

For instance, if there’s a local variable available named d, then d won’t get replaced with nt.d:

julia> let
           nt = (;a=1, b=4.0, c="hi", d=true)
           d = false
           with(nt) do
               if !d
                   (a - b)/(a + b)
               else
                   c
               end
           end
       end
-0.6

I’m sure there are other problems and corner cases, but I learned a lot writing this so I thought I’d share.

2 Likes