Bouncing out from some recent discussions on the github issue tracker, it 
seems like there's some interest in tuples in Go. I thought the discussion 
in #66651 led to some interesting ideas, but it's also beginning to drift. 
Maybe this is a better place to brain-dump some ideas. (This could be a 
proposal but I'm not sure that's quite right either, that might be spammy.)

Some recent issues:
1. #64457 "Tuple types for Go" <https://github.com/golang/go/issues/64457> 
(@griesemer)
2. #66651 "Variadic type parameters" 
<https://github.com/golang/go/issues/66651> (@ianlancetaylor)
3. "support for easy packing/unpacking of struct types" 
<https://github.com/golang/go/issues/64613> (@griesemer)

Synthesizing from those discussions, and satisfying requirements framed by 
@rogpeppe 
<https://github.com/golang/go/issues/66651#issuecomment-2054198677>, the 
following is a design for tuples that comes in two parts. The first part 
explores tuples in non-generic code, resembling a restrained version of 
#64457. The second part explores tuple constraints for generic code, 
reframing some ideas from #66651 in terms of tuples. It's a fungal kingdom 
approach, where tuples occupy some unique niches but aren't intended to 
dominate the landscape.

*TUPLES IN NON-GENERIC CODE*

Tuples are evil 
<https://github.com/golang/go/issues/32941#issuecomment-509367113> because 
the naming schemes are deficient. To enjoy greater name abundancy, this 
design tweaks tuple *types* from #64457 in the direction of "super-lightweight 
structs" <https://github.com/golang/go/issues/64457#issuecomment-1834358907>. 
It still allows tuple *expressions* from #64457, for tuples constructed 
from bare values.

*1. Tuple types*
Outside of generics, tuple *type* syntax requires named fields.

TupleType = "(" { IdentifierList Type [ ", " ] } ")" .

// e.g.:
type Point (X, Y int)

More irregularly, the TupleType syntax is used *exclusively* to declare 
named types, and these named tuple types cannot implement methods. As a 
result, a named tuple type is entirely defined at the site of the type 
definition.

*2. Tuple literals*
The tuple *expression* syntax of #64457 remains valid. The result is an 
implicitly typed tuple value. Literals of a named tuple type are also 
valid, and resemble struct literals.

point1 := (0, 0) // implicitly typed
point2 := Point(X: 0, Y: 0) // explicitly typed


*3. Promotion and expansion*
There is no way to capture the type of an implicitly typed tuple value - 
the result of a bare tuple *expression* - with tuple *type* syntax. 
However, promotion and expansion are available as way to leverage tuple 
values.

- Promotion: An implicitly typed tuple value is freely and automatically 
promoted to a value of a named tuple type, if and only if the sequence of 
types is congruent (same types, same order, same arity) between the 
implicit and named type:

type T (string, string)
var t T
t := ("foo", "bar")

The RHS of the assignment is implicitly typed (string, string), so the 
value can be promoted to the LHS's congruent type T without further 
ceremony.

- Any tuple value can, under the condition of congruence, expand with ... 
"wherever 
a list of values is expected" (#66651). This means places like assignments, 
function calls, function returns, struct/slice/array literals, for/range 
loops, and channel receives. Each of the github issues (#64457, #64613, 
#66651) explores this in more detail. Qualifications and some subjectivity 
are involved, and a full proposal would explore this more completely and 
sharply, but the intuitive notion is pretty straightforward.


*TUPLE CONSTRAINTS*
For generic code, this design's driving concept is tuple constraints. A 
tuple constraint describes type sets that are exclusively composed of tuple 
types. Loosely, where union-of-types or set-of-methods type constraints are 
currently, a tuple constraint would also be allowed. The rules for code 
parameterized on tuple constraints should resemble #66651 in many ways. 
Most essentially, it should be possible to substitute a tuple constraint 
"wherever a list of types is permitted", as suggested in #66651.


*1. Non-variadic tuple constraints*
The current TypeParamDecl production is:

TypeParamDecl = IdentifierList TypeConstraint .

Adding tuple constraints can be accomplished by extending TypeParamDecl syntax 
to include an alternative to the TypeConstraint, a TupleConstraint. Then, a 
tuple constraint is constructed from TypeConstraint elements.

TypeParamDecl = IdentifierList ( TypeConstraint | TupleConstraint ) .
TupleConstraint = "(" { TypeConstraint [ "," ] } ")" .

Some examples:
[T (any, any)] describes the type set consisting of any 2-ary tuple
[T (K, any), K comparable] describes the type set of 2-ary tuples that 
begin with a comparable element.

Via tuple -> list-of-types substitution, the following would be equivalent:

func F[K comparable, V any](f func(K, V)) { ... }
func F[KV (comparable, any)](f func(KV)) { ... }

*2. Variadic tuple constraints*

A variadic tuple constraint is described with an extension to the 
TupleConstraint production: an optional VariadicTupleElement is appended to 
it.

TupleConstraint = "(" { TypeConstraint [ "," ] } [ VariadicTupleElement ] 
")" .
VariadicTupleElement = "..." TypeConstraint .

The identifier for a variadic tuple constraint may be still be substituted 
for a list of types. Drawing from use cases discussed in #66651, this leads 
to function signatures like:

func Filter[V (... any)](f func(V), seq Seq[V]) Seq[V]

func MergeFunc[V (... any)](xs, ys Seq[V], f func(V, V) int) Seq[V]

Additionally, tuple constraints can accommodate multiple variadic type 
parameters:

func Zip[T0 (... any), T1 (... any)](xs Seq[T0], ys Seq[T1]) Seq[Zipped[T1, 
T2]]

func Memoize[In (... comparable), Out (... any)](f func (In) Out) func(In) 
Out

*3. Instantiation and unification*

Like #66651, variadic type parameters are only instantiated by non-variadic 
types. Unification of a concrete tuple type with a tuple constraint 
considers the compatibility of tuple and constraint arity, and 
compatibility of tuple and constraint elements.

When unifying type parameters, tracking fixed or minimum arity is 
significant. Note that the fixed arity of a non-variadic tuple constraint 
and the minimum arity of a variadic tuple constraint is implicit in the 
notation. For example:

[T (any, any)] -> any 2-ary tuple
[T (any, any, any, ... any)] -> any tuple of arity 3 or greater

The intersection of any two tuple constraints is calculable, composable, 
and order independent. (Or, at least the arity question has these 
properties, and I believe the per-element question is a well - as I 
understand that's an important property of unification currently.)

*Further questions*

- The inverse of tuple constraint -> list-of-type substitution, inferring a 
tuple constraint from a list of types, seems tractable. Maybe it's even 
useful.
- This design doesn't propose ... unpacking for structs, as suggested in 
#64613. Is something here helpful?
- This design only allows a single trailing variadic element in a tuple 
constraint. Comments on #66651 explored uses that would require a single 
leading variadic element. I don't know whether or not this works formally, 
but it's intriguing.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/32547c6c-a9c0-4f9d-8a27-41e9173ba85dn%40googlegroups.com.

Reply via email to