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.