# FrankenTuple Fun

**URL:** <https://discourse.julialang.org/t/frankentuple-fun/91507>\
**Category:** Internals & Design\
**Tags:** macros, experimentation, frankentuple\
**Created:** [December 10, 2022, 11:18am UTC](https://discourse.julialang.org/t/frankentuple-fun/91507 "2022-12-10T11:18:32Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [December 10, 2022, 11:18am UTC](https://discourse.julialang.org/t/frankentuple-fun/91507/1 "2022-12-10T11:18:32Z")

</div>

> [@Fixing the Piping/Chaining Issue (Rev 3)](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue-rev-3/90836/61):
>
> > [@Fixing the Piping/Chaining Issue (Rev 3)](https://discourse.julialang.org/t/fixing-the-piping-chaining-issue-rev-3/90836/60):
> >
> > @jar1: We already have a shorter alternative to `begin a; b end` for blocks: `(a; b)` 😉
> 
> Yes, but `(a; b)` is horrible. That should be syntax for [FrankenTuples](https://ararslan.github.io/FrankenTuples.jl/latest/).

and

> [@Compact syntax for combining \`Tuple\` and \`NamedTuple\`: \`(1, 2; x=2, y=3) == ((1, 2), (x=2, y=3))\`](https://discourse.julialang.org/t/compact-syntax-for-combining-tuple-and-namedtuple-1-2-x-2-y-3-1-2-x-2-y-3/78101/11):
>
> I regret making `(ex; ex)` a chained expression syntax. Not useful enough to be worth it.

and

> [@Compact syntax for combining \`Tuple\` and \`NamedTuple\`: \`(1, 2; x=2, y=3) == ((1, 2), (x=2, y=3))\`](https://discourse.julialang.org/t/compact-syntax-for-combining-tuple-and-namedtuple-1-2-x-2-y-3-1-2-x-2-y-3/78101/6):
>
> Maybe an intermediate solution would be a macro that turns everything of the form `(1, 2; x=2, y=3)` into a `FrankenTuple` in a code block

> [@Compact syntax for combining \`Tuple\` and \`NamedTuple\`: \`(1, 2; x=2, y=3) == ((1, 2), (x=2, y=3))\`](https://discourse.julialang.org/t/compact-syntax-for-combining-tuple-and-namedtuple-1-2-x-2-y-3-1-2-x-2-y-3/78101/13):
>
> What if we required a trailing comma for that case?

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

 ![image](https://global.discourse-cdn.com/julialang/original/3X/8/5/8548784dd779038a7399730c1429bf2a3c07ea06.png)

An experiment with `FrankenTuple`s:

```julia
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[1] isa Expr && ex.args[1].head == :parameters
               t = Expr(:tuple, ex.args[2:end]...)
               nt = Expr(:tuple, ex.args[1])
               ex.head = :call
               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
julia> @ft x=(1,; a=2)
FrankenTuple((1,), (a = 2,))

```

Better yet:

```julia
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
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
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
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!
