Is it possible to define a pairwise function that can either append to a tuple or make a tuple, potentially with nested tuples? I lack the vocabulary to appropriately express the question succinctly, so I apologize if the title question is confusing.

Suppose you can define a binary infix operator that produces tuples, and you want this operator to be able to simultaneously:


Extend a tuple to arbitrary length by appending additional elements
Support nested tuples (in that they are not "flattened" into the tuple)


Suppose the operator is represented by ., I can do (1.) in pseudocode by:

define generic &lt;Type T (T != Tuple), Type U&gt;
operator (.) (T left, U right) =&gt; Tuple&lt;T, U&gt; {
  createTuple(left, right);
}

define generic &lt;Type... T, Type U&gt;
operator (.) (Tuple&lt;...T&gt; left, U right) =&gt; Tuple&lt;...T, U&gt; {
  appendTuple(left, right);
}


This violates (2.) though, if the leftmost value is a tuple:

# Good
1 . 2 . 3 . 4 -&gt; (1, 2) . 3 . 4 -&gt; (1, 2, 3) . 4 -&gt; (1, 2, 3, 4)

# Also good
1 . (2, 3) . 4 -&gt; (1, (2, 3)) . 4 -&gt; (1, (2, 3), 4)

# Bad
(1, 2) . 3 . 4 -&gt; (1, 2, 3) . 4 -&gt; (1, 2, 3, 4)


What I'd like is to get ((1, 2), 3, 4) for the last example. The only way I can think to do this would require constraining the types, which I would like to avoid. Is something like this even possible, in any programming language or type system?
The problem with this is that there is no way to distinguish between (1, 2) and 1 . 2: both have to produce (1, 2). This means that for a left-associative ., 1 . 2 . 3 and (1, 2) . 3 have to produce the same result. (And switching to right-associative . does not help: it has the same issue with 1 . (2, 3).)

Some ways I could think of bypassing this:


The . operator does not directly produce a tuple, instead it produces a wrapper for a tuple. This way, you can distinguish between 1 . 2 and (1, 2). But you also have to include an "unwrap" operation at the end. Though that could be handled by an implicit conversion, which makes the unwrap operation invisible in code.
The . operator looks like it's binary, but it's actually variadic. I.e. (1, 2) . 3 . 4 compiles to operator(.) ((1, 2), 3, 4). Though I don't know of any language that would do something like this.