# FrankenTuple Fun

and

and

Turns out that `(a,; b)` parses, and has for awhile (according to this time machine):

An experiment with `FrankenTuple`s:

``````julia> using FrankenTuples

julia> macro ft(ex)  esc(ft!(ex))  end
@ft (macro with 1 method)

julia> function ft!(ex)
# FrankenTuple Construction
if ex isa Expr && ex.head == :tuple && length(ex.args) > 1 &&
ex.args isa Expr && ex.args.head == :parameters
t = Expr(:tuple, ex.args[2:end]...)
nt = Expr(:tuple, ex.args)
ex.args = [:FrankenTuple, t, nt]
end
ex isa Expr && map(ft!, ex.args)
ex
end
ft! (generic function with 1 method)
``````

Can be called like:

``````julia> @ft x=(1,; a=2)
FrankenTuple((1,), (a = 2,))
``````

Better yet:

``````julia> pushfirst!(Base.active_repl_backend.ast_transforms, ft!);

julia> x=(1,; a=2)
FrankenTuple((1,), (a = 2,))
``````

Wrap any other expression in `@ft` and its behavior is unchanged. For example:

``````julia> @ft (1; a=2)
2

julia> @ft (1, 2, 3)
(1, 2, 3)

julia> @ft (a=1, b=2)
(a = 1, b = 2)
``````

Of course, `@ft` is kinda redundant after adding `ft!` to our REPL AST transforms.

More `FrankenTuple` fun:

``````julia> (1, 2; a=3, b=4)
FrankenTuple((1, 2), (a = 3, b = 4))

julia> (1, 2; )
FrankenTuple((1, 2), NamedTuple())

julia> (()...; a=3, b=4)
FrankenTuple((), (a = 3, b = 4))

julia> (()...; )
FrankenTuple()

julia> ((1,2,3)...; (a=4, b=5, c=6)...)
FrankenTuple((1, 2, 3), (a = 4, b = 5, c = 6))
``````

Keep in mind, this is an experiment! I’m just playing around to see what’s possible.

Having to splat an empty tuple to make the FrankenTuple’s tuple an empty tuple is weird While `()` is an empty `Tuple` and `(;)` is an empty `NamedTuple`, an edit is needed for `(,;)` to parse so it can be an empty `FrankenTuple`.

The `FrankenTuple`’s splatting behavior is wrong. Iterating over `(x...,)` should splat only the numbered elements from its tuple (right now it splats both numbered and named elements), and for named elements `(;x...)` a custom `Base.merge(nt::NamedTuple, ft::FrankenTuple) = merge(nt, getfield(ft, :nt))` should be implemented that splats out its named elements. Then you could write `f(x...; x...)` and each part would splat out as expected  Like this:

``````julia> Base.iterate(x::FrankenTuple) = iterate(getfield(x, :t))

julia> Base.iterate(x::FrankenTuple, n) = iterate(getfield(x, :t), n)

julia> Base.merge(nt::NamedTuple, ft::FrankenTuple) = merge(nt, getfield(ft, :nt))

julia> f(args...; kwargs...) = (args, kwargs)
f (generic function with 1 method)

julia> f((1,)...; (a=2,)...)
((1,), Base.Pairs(:a => 2))

julia> f(x...; x...)
((1,), Base.Pairs(:a => 2))
``````

GLHF!

3 Likes