source: doc/proposals/tuples.md@ d3942b9

Last change on this file since d3942b9 was 1b770e40, checked in by Peter A. Buhr <pabuhr@…>, 12 months ago

first proofread of tuple proposal

  • Property mode set to 100644
File size: 28.6 KB
Line 
1Tuples
2======
3
4Tuples were introduced by Dave Till in K-W C, added to CFA by Rodolfo Esteves,
5and extended by Robert Schluntz. This proposal discusses updates for CFA tuples
6to address problems that have appeared in their usage over the past 6 years.
7The proposal attempts to address problems by adding new functionality, updating
8existing features, and removing some problematic ones.
9
10The core change is breaking the current restructurable tuples into unstructured
11and structured tuples. Unstructured tuples handle most of the existing uses,
12with structured tuples filling in a few missing use cases.
13
14Current State of Tuples
15-----------------------
16An overview of the current tuples design is the starting place for the proposal.
17
18### Tuple Syntax
19
20Currently, tuples have three main components: tuple types, tuple
21expressions/values (constructing tuples), and tuple index expressions
22(deconstructing tuples).
23
24Current syntax for tuple types.
25
26- Nullary: [void] or []
27- Unary: [TYPE]
28- Nary: [TYPE, TYPE, ...]
29
30Tuple types can appear in a function return and parameter declaration, or a
31tuple variable declaration. Note, the Nullary tuple is only meaningful in the
32return context,
33
34 void f(...); // equivalent
35 [void] f(...);
36 [] f(...);
37
38as C does not support a void declaration.
39
40 int f( void x ); // disallowed
41 int f( [void] x );
42 int f( [] x );
43
44Current syntax for tuple expressions:
45
46- Nullary: (Same as `void`, use return without an expression.)
47
48 [] f( ) { return; }
49 [] f( ) { return [] ; }
50
51- Unary:
52
53 [int] f( ) { return 3; }
54 [int] f( ) { return [3]; }
55
56- Nary: [EXPR, EXPR]
57
58 [int,int] f( ) { return [3,4]; }
59
60Currently, there is a parsing problem for nullary and unary tuple expression,
61which is being looked at. Hence, only these two forms work.
62
63 [] f( ) { return; } // nullary
64 [int] f( ) { return 3; } // unary
65
66Current syntax for tuple indexing is an integer constant, where its value
67selects a tuple member, e.g.:
68
69 [char, int] tup;
70 tup = ['a', 0];
71 char ch = t.0; // select first tuple member
72 int value = t.1; // select second tuple member
73
74### Mass and Multi-Assignment
75
76Two special forms of assignment can be used to set values in tuples: mass and
77multi. Mass assignment assigns every element in the destination tuple to a
78single source value.
79
80 [int, long, float] dst;
81 int src = 4
82 dst = src; // => dst.0 = src; dst.1 = src; dst.2 = src
83
84Multi-assignment assigns every element in the destination tuple to the
85corresponding element in the source tuple. Both tuples must be the same size
86and the elements assignment compatible => conversions.
87
88 [long, int, float] dst;
89 [int, char, double] src = [1, 'a', 300.0];
90 dst = src; // => dst.0 = src.0; dst.1 = src.1; dst.2 = src.1
91
92### Tuple Restructuring
93
94Tuples can be restructured as part of overload resolution. Function calls
95unpack tuples and repack tuples to match signatures. This semantics is a form
96of implicit conversion and is considered during overload resolution.
97
98A simple example is matching multiple parameters of a function to a single
99argument expression, where each parameter is bound to a different element of
100the returned tuple.
101
102 [int, int] argFunc();
103 void parmFunc(int a, int b, int c, int d);
104
105 parmFunc(argFunc(), argFunc());
106
107 // Roughly equivilent to:
108 [int, int] fst = argFunc();
109 [int, int] snd = argFunc();
110 parmFunc(fst.0, fst.1, snd.0, snd.1);
111
112There are few languages supporting multiple return-values as a standalone
113feature (SETL). Go has multiple return-values but restricts their use in
114matching arguments to parameters.
115
116 func argFunc() (int, int) {
117 return 3, 7
118 }
119 func parmFunc( a int, b int ) {
120 fmt.Println(a, b )
121 }
122 func main() {
123 parmFunc2( argFunc() ); // arguments must match exactly with parameters
124 }
125
126### Tuple Casts
127
128C-style casts can be used on tuples. These are usually conversion casts (casts
129that perform operations on the cast type, as opposed to reinterpreting the
130existing value).
131
132Tuple casts can remove elements from the end of a tuple and apply a recursive
133cast to any of the elements. As an example:
134
135 [char, char, char] x = ['a', 'b', 'c'];
136 ([int, char])x;
137
138This casts the first element type of x from a char to an int and drops the last
139element. The second line can be replaced with the following code, which creates
140a new tuple by directly casting the kept elements of the old tuple:
141
142 [(int)x.0, (char)x.1];
143
144Note, tuple casting is more restricted than the implicit tuple restructuring.
145It cannot do any restructuring beyond dropping the trailing elements of
146tuples. For example,
147
148 int i; char c; bool b;
149 [i, b, c, i] = [i, [b, c], i];
150 [int, [bool, char], int] w;
151 [i, b, c, i] = w;
152 [i, b, c, i] = ([int, bool, char, int])w; // fails
153 [i, b, c] = ([int, [bool, char]])w; // works
154
155### Polymorphic Tuple Parameters
156
157One kind of polymorphic parameter is the tuple type parameter, previously
158noted by the `ttype` keyword and now marked by a tailing ellipses (`...`).
159Although described as a tuple type, it is usually viewed as its flattened
160sequence of types.
161
162 forall( T | { int ?>?( T, T ); } )
163 T max( T v1, T v2 ) { return v1 > v2 ? v1 : v2; } // base case
164
165 forall(T, Ts... | { T max(T, T); T max(Ts); }) // recursive case
166 T max(T arg, Ts args) {
167 return max(arg, max(args));
168 }
169
170This feature introduces a type name into scope (Ts). It is used as a type but
171because the tuple is flattened, the second assertion "T max(Ts);" matches types
172with multiple parameters (the `...`), although it is used as a tuple function
173inside the function body (max(args)).
174
175The first non-recursive max function is the polymorphic base-case for the
176recursion, i.e., find the maximum of two identically typed values with a
177greater-than (>) operator. The second recursive max function takes two
178parameters, a T and a Ts tuple, handling all argument lengths greater than two.
179The recursive function computes the maximum for the first argument and the
180maximum value of the rest of the tuple. The call of max with one argument is
181the recursive call, where the tuple is converted into two arguments by taking
182the first value (lisp car) from the tuple pack as the first argument
183(flattening) and the remaining pack becomes the second argument (lisp cdr).
184The recursion stops when the tuple is empty. For example, max( 2, 3, 4 )
185matches with the recursive function, which performs return max( 2, max( [3, 4]
186) ) and one more step yields return max( 2, max( 3, 4 ) ), so the tuple is
187empty.
188
189
190Issues with the Current State
191-----------------------------
192There are a variety of problems with the current implementation which need to
193be fixed.
194
195### Tuples are not Objects
196
197Spoilers: this proposal actually takes them even further away from being
198objects, but illustrates why the current version is not as useful is it could
199be.
200
201Because of the fluid nature of tuples (flattening/structuring), routines like
202constructions, destructor, or assignment do not make sense, e.g. this
203constructor matches multiple a tuple types:
204
205 void ?{} ( [int, int, int] ? this );
206 [int, [int, int]] x; // all match constructor type by flattening
207 [int, int, int] y;
208 [[int, int], int] z;
209
210as could a similarly typed destructor or assignment operator. This prevents
211tuples from being interwoven with regular polymorphic code.
212
213### Providing TType Arguments is Inconsistent
214
215The syntax for ttype arguments is slightly inconsistent. It has not come up
216much yet, because you do not directly provide ttype polymorphic arguments to
217functions and there are very few existing use-cases for ttype structures.
218
219Passing arguments to a function inlines the arguments
220while passing them to a polymorphic type requires them to be
221enclosed in a tuple. Compare `function(x, y, z)` with `Type(A, [B, C])`.
222
223This did not come up previously as there was little reason to explicitly
224provide ttype arguments. They are implicit for functions and there is very
225little use case for a ttype on a struct or union.
226
227### Syntax Conflict
228
229The tuple syntax conflicts with designators and the new C++-style attribute
230syntax.
231
232 struct S { int a[10]; } = { [2] = 3 }; // [2] looks like a tuple
233 [[ unused ]] [[3, 4]]; // look ahead problem
234
235These conflicts break C compatibility goals of Cforall. Designators had to
236their syntax change and Cforall cannot parse the new attributes.
237
238Although most of this redesign is about the semantics of tuples, but an update
239to tuple syntax that removes these conflicts would improve the compatibility of
240Cforall going forward (and also open up the new attribute syntax for cforall
241features).
242
243Change in Model
244---------------
245This proposal modifies the existing tuples to better handle its main use
246cases. There are some use cases that are no longer handled, and a new
247struct tuple is added to cover those cases.
248
249The new tuples is even more "unstructured" than before. New tuples are
250considered a sequence of types or typed entities. These packs are then unpacked
251into the surrounding context. Put differently, tuples are now flattened as much
252as possible, with some places (like parameter lists) being treated as an
253implicit tuple and the tuple being flattened into that.
254
255Structured tuples are now a separate feature: a structure called "tuple".
256These are polymorphic structures; an instance should act as a structure, except
257its fields are accessed using indices instead of field names. Experience so far
258is that structured tuples are not used often, but fill in the use cases that
259unstructured tuples no longer support.
260
261Note that the underlying implementation might not actually look like this.
262
263Changed Features
264----------------
265Some of the concrete changes to the features of the language.
266
267### Structured Tuple Type
268
269There is a standard library or built-in type named `tuple`, it does not need a
270special syntax to write types or instances. The type definition might need some
271primitive support, but if supported as a regular type would look something like
272this:
273
274 forall(Ts...)
275 struct tuple {
276 inline Ts all; // inline all specified fields
277 };
278
279This type is constructed the same way as most types, a list initializer with
280each tuple argument, and the lifetime functions (construction, assignment and
281destruction) work the same. Field access works two ways, the first is accessing
282the `all` field, effectively converting the structured tuple into an
283unstructured tuple, the other is to use tuple indexing directly on the
284structure as if it is an unstructured tuple.
285
286(If `inline` does not work, just rename all to `get`. It does make things a bit
287longer but has no change in functionality. If the `all` access does not work,
288that is more problematic, and tuple slicing might provide a work around.)
289
290### Type Qualifier Distribution
291
292Because tuples are no longer object types, applying type modifiers to them,
293such as cv-qualifiers and pointers, no longer makes sense. That syntax is
294now considered distribution, applying the type modifier to each element of
295the tuple.
296
297Previously `const [int, bool] &` would mean a const reference to a tuple of an
298integer and a boolean. Now it means an alias for `[const int &, const bool &]`,
299a tuple of a reference to a constant integer and a reference to a constant
300boolean. This also applies to polymorphic tuple type packs `Ts &` in
301polymorphic functions.
302
303This new approach can specify restrictions on tuple variables as for a single
304type variable. For example, this approach can replace the special cased tuple
305operations multi-assignment (N-to-N) and mass-assignment (1-to-N).
306
307 // Multi-Assignment
308 forall(T, Us... |
309 { T & ?=?(T &, T const &); Us & ?=?(Us &, Us const &); })
310 [T, Us] & ?=?(T & dst, Us & dsts, T const & src, Us const & srcs) {
311 dst = src;
312 dsts = srcs;
313 return [dst, dsts];
314 }
315
316 // Mass-Assignment
317 forall(T, U, Vs... |
318 { U & ?=?(U &, T const &); Vs & ?=?(Vs &, T const &); })
319 [U, Vs] & ?=?(U & dst, Vs & dsts, T const & src) {
320 dst = src;
321 dsts = src;
322 return [dst, dsts];
323 }
324
325These may not work exactly as given (for one, the copy assignment assertion
326in the first function would likely be redundant/conflicting with the implicit
327assertions on the parameters), but they show the pattern. Multi-assignment
328also would be very hard to write with simple tuple types, because the
329relationship between the two halves of the parameter list does have to line
330up, that cannot be enforced with two different tuples.
331
332### Type Packs
333
334This approach is not a new feature, but a reframing/extension of existing tuple
335tuple polymorphic parameters as polymorphic type packs. The old `Vars...`
336syntax introduces a pack of types into scope. It can be used in much the same
337way as a tuple, but in some new ways to.
338
339The primary existing use remains: to use a polymorphic pack in a parameter
340list, both as part of an assertion and in the signature of the main
341function. The difference is that this is not an enclosed tuple, but a series of
342types. The only effective difference this makes is it does not prefer to match
343another tuple/pack.
344
345This pattern continues to a parameter defined with a pack of types, which
346is considered a pack of parameters, and the name it introduces is a pack
347of variables, or a flattened tuple.
348
349 forall(Params...)
350 void function(Params values);
351
352New use cases include declarations of members and variables. For example, the
353creation of a structured tuple structure:
354
355 forall(Fields...)
356 struct tuple {
357 Fields get;
358 };
359
360This is again, treated as a pack of members. They have the same layout as
361if they were written out by hand. Now, the name get is still accessed as if
362it was a regular, singular, member. The result of that expression is a
363pack expression, a tuple of all the field accesses, which can be used with a
364tuple index expression to access the underlying members. For example:
365
366 tuple(bool, char, int) data = { ... };
367 // This expression:
368 data.get.2;
369 // Is the same as (if the fields had these names):
370 data.[__anonymous0, __anonymous1, __anonymous2].2;
371
372For local declarations it works similarly, except the name introduced is
373directly usable as a tuple.
374
375 forall(Objects...)
376 void function( ??? ) {
377 ???
378 Objects objs = ???;
379 ???
380 }
381
382### Tuple Declaration/Deconstruction
383
384Declaring a tuple acts as a pack of variable declarations. When this is done
385with a written out type (as opposed to a polymorphic parameter above), then the
386elements of the tuple can be named.
387
388 [int quo, int rem] ret = divmod(a, b);
389
390Here `ret` refers to the tuple, the entire pack, while `quo` and `rem`
391give explicit names to elements of the tuple. Not all the names have to be
392provided, at the very least, any element name can be omitted if the pack name
393is provided, and the pack name can be omitted if all the element names are
394provided. That would ensure every element can be accessed, but it could be
395reduced even more, effectively immediately dropping some values if there is
396no named to access it.
397
398PAB: I do understand the point of this.
399
400### Tuple Casts
401
402Tuple casts are no longer allowed to do any restructuring. Internal
403restructuring would not be useful, as there is no internal structure.
404Dropping tail elements could be added back in but it is a narrow use case
405so it may be replaced with other features (for example: tuple slicing).
406
407### Forbidden Tuples
408
409The unstructured tuple cannot represent all the types that the previous
410semi-structured tuple could. These cases still exist in various ways,
411specifically in the internals of a polymorphic type, but in general should be
412considered in their reduced form.
413
414Forbidding some of these tuples may remove ambiguity and solve some syntax
415conflicts that currently exist.
416
417Nullary, or 0 element tuples, are equivalent to void, the type that carries
418no data, because they do not either. It should either be replaced with void
419or removed entirely when it appears in a larger sequence.
420
421 // For example, this function:
422 forall(Ts...) Ts example(int first, ????)
423 // With Ts = [], should not be treated as:
424 [] example(int first, [] middle, int last);
425 // But instead:
426 void example(int first, int last);
427
428Unary, or 1 element tuples, should be the same as their element type. That
429is to say a single type in an unstructured tuple is a no-op.
430
431Lastly, nested tuples are always flattened to a one-depth tuple. This means
432that `[bool, [char, int], float]` is resolved as `[bool, char, int, float]`,
433with the internal structure of the tuple ignored.
434
435The flatten into a large sequence rule mentioned above is actually just an
436application of this. Unstructured tuples can already be restructured, even at
437the top level of an function call. This can be expressed by considering the
438argument list as a tuple:
439
440 call(false, ['a', -7], 3.14)
441 call([false, ['a', -7], 3.14])
442 call([false, 'a', -7, 3.14])
443 call(false, 'a', -7, 3.14)
444
445The ability to write nested tuples may remain so tuple deconstruction can be
446used to name a slice of the tuple.
447
448### Tuple Slicing (Range Tuple Indexing)
449
450(Disclaimer: this is a bit more impulsive, see end for notes.)
451
452This is an extension to tuple indexing. Currently, only single location of a
453tuple can be index, extracting the element at that location. By extending the
454index expression to be a list-range a multiple sub-tuples can be extracted.
455
456 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].0~2,5~9~2
457
458produces the tuple
459
460 [0, 1, 2, 5, 7, 9]
461
462(Note the position and values are the same to simplify the example.) That is,
463index the first 3 tuple elements, and then indexes elements 5 to 9 in steps of
4642. Not all selections are possible with this mechanism (e.g., index the
465Fibonacci elements), but it covers many cases.
466
467To get the new tuple, the range has to be constructed and traversed at compile
468time. The size of the range is the size of the result tuple, with each element
469in the result tuple decided by the matching element of the range, which gives
470the index of the original tuple to place there. The type of the indices may be
471an integral type, but all indices must be in range, otherwise it is a compile
472time error.
473
474In terms of the existing range loops, if you could write this dynamically, it
475would look something like this:
476
477 // Implementation of: output = input.RANGE
478 dyn output = []
479 for ( index : RANGE ) { output = [output, input.index] }
480
481The result of the expression is only has to be considered a tuple if the
482range has two or more values, otherwise it can be considered void or the same
483as indexing the single value in the range.
484
485Some closing notes, this is dependent on generalized range expressions.
486The iterators proposal has a section on some new range types, this feature
487is intended to be built on that feature. Not simply reuse some of the syntax
488that is also used in the special for loop. And compile-time evaluation may
489need to be improved.
490
491PAB, I don't understand this last part as the index range is compile time not
492runtime.
493
494Implementation
495--------------
496An overview of the implementation details of the new proposal.
497
498### Structured Tuple Implementation
499
500Under the hood, unstructured tuples are implemented as structured tuples, with
501the restructuring code inserted wherever needed. In short, the base
502implementation should stay mostly the same.
503
504PAB: The current implementation does not use convert unstructured tuples to
505structured tuples. Look at the code generated for
506
507 int x, y;
508 [x, y] = 3;
509 [x, y] = [y, x];
510
511
512Actually inlining tuples can be done in some cases, it may even help with
513some forms like a fixed tuple decomposition. However, polymorphic tuples
514still need to be reduced to a single generic form, which would be a
515polymorphic container, a tuple.
516
517### AST Updates
518
519The current AST cannot represent all the new features. Particularly, an
520object declaration cannot name elements of the tuple. To this end a new
521node type, `TupleDecl`, should be added to handle tuple deconstruction
522declarations (other cases can still be handled with `ObjectDecl`).
523
524This would act much like a `FunctionDecl` except for tuples, narrowing the
525possible types, to `TupleType` instances instead of `FunctionType` instances,
526and storing some additional information. In this case, the names of the
527elements of the tuples.
528
529PAB: the following parses:
530
531 [int x, int y] foo( int p );
532
533and discussed by Till.
534
535(I'm not actually going to decide the implementation now, but some early
536examination of the problem suggests that it might be better off wrapping a
537series of `ObjectDecl` rather than just some strings to hold the names.)
538
539### Field Packs
540
541Field packs in structures probably have to be written out in full by the
542specialization pass. If not, it could have some negative effects on layout,
543causing a structure to take up extra space. It may be able to reuse some of the
544specialization code for the existing tuples.
545
546Related Features in Other Languages
547-----------------------------------
548Other languages have similar features. Organized by the related feature.
549
550(In hindsight, I may have gone overboard with the number of examples.)
551
552### Structured Tuples
553
554There are many languages with structured tuples. Usually just called tuples,
555but they usually follow the same rules as a polymorphic anonymous structures,
556that is to say N types are provided to create a new N-arity tuple. They also
557usually have some special syntax to index the tuples, because there are no
558field names.
559
560#### Rust
561
562Rust has the standard tuples as a primitive in the language. Each arity of
563tuple is a different polymorphic type.
564
565Tuple types and expressions are written with a parenthesized, comma separated
566list of types or expressions. To avoid confusion with the grouping "(...)",
567one-element tuples must have a trailing comma (and larger tuples may have a
568trailing comma).
569
570 const empty: () = ();
571 const single: (usize,) = (12,)
572 const double: (usize, isize) = (34, -56);
573
574Element access uses similar syntax to field access (that is "."), but uses an
575integer literal instead of a field name (for example: "pair.1"). Tuples
576support pattern matching with similar syntax to their expression form.
577
578Some tuple features only apply up to 12-arity tuples.
579It is not directly stated, but I believe the difference in the more limited
580features are implemented in the standard library, one for each arity of tuple.
581
582- https://doc.rust-lang.org/std/primitive.tuple.html
583- https://doc.rust-lang.org/reference/types/tuple.html
584- https://doc.rust-lang.org/reference/expressions/tuple-expr.html
585
586#### C++ tuple
587
588Implemented as a template type defined in the standard library. No special
589language features exist to support this, due to the power of C++'s template
590meta-programming.
591
592C++ is also one of the few languages with support for variadic polymorphic
593types, so there is one template that defines all the types. It is written
594as `std::tuple<TYPE...>`, where "TYPE..." is any number of comma separated
595types. This is the standard notation for template type instances.
596
597There is no special syntax for member access of a tuple. A template function is
598used, e.g., `std::get<0>( tuple )`.
599
600C++ also has structured binding, a kind of limited pattern matching. In a
601structured binding declaration, you can write an auto typed declaration with
602a list of identifiers in a `[]` list.
603For example, `auto [first, second] = getSize2Tuple();`.
604
605- https://en.cppreference.com/w/cpp/utility/tuple
606- https://en.cppreference.com/w/cpp/language/structured_binding
607
608PAB: I do not understand the syntax `auto [first, second]`. Where does it come
609from?
610
611#### C++ template
612
613C++ templates can take various types of parameters, including parameter
614packs. These contain series of values. These are used in pack expansion,
615which usually expand to a comma separated list, but it can also be a chain of
616boolean binary operator applications. For example, if the parameter
617`typename... Ts` is in scope, then you can declare `std::tuple<Ts...>` to
618introduce a tuple type, if Ts = bool, char, int then the type becomes
619`std::tuple<bool, char, int>`.
620
621A common use is for perfect argument forwarding, which shows some different
622uses of the pattern:
623
624```
625template<typename inner_t>
626class Outer {
627 inner_t inner;
628public:
629 template<typename... Args>
630 Outer(Args&&... args) : inner(std::forward<Args>(args)...) {}
631};
632```
633
634In the first application, `Args&&... args` both uses a pack and introduces
635another one. `Arg0&& arg0, Arg1&& arg1, Arg2&& arg2, ...` is the pattern
636it expands too. The `&&` is used to copy the argument type's reference
637qualifier (the default can strip references away).
638
639The second application, `std::forward<Args>(args)...` uses two packs. These
640are expanded in parallel, and must be the same length (in this case they
641always will be). It also helps show that the `...` is actually the bit doing
642the expansion, it is a suffix "operator" to the expansion pattern.
643
644There are also fold expressions that use binary operators to combine a pack
645into a single expression. For example, `args + ... + 0` which adds every
646element of the `args` pack together.
647
648C++ is about the best you could ask for in this area, but it does a lot of work
649at compile time to make this happen.
650
651- https://en.cppreference.com/w/cpp/language/template_parameters
652- https://en.cppreference.com/w/cpp/language/parameter_pack
653- https://en.cppreference.com/w/cpp/language/fold
654
655#### Haskell
656
657Haskell has a special syntax for tuples, but otherwise they are completely
658normal polymorphic types. Because of this, tuples have a maximum size.
659Haskell (98) supports tuples of up to 15 elements and the standard library
660has functions for tuples of up to 7 elements.
661
662The syntax for tuples is a comma separated list of elements. Either element
663types to create a tuple type, or element values to create a tuple value. The
664context decides among them, such as `(6, "six")` or `(Bool, Char, Int)`.
665
666Also all the elements can be removed, getting an expression like "(,)" or
667"(,,,)", which can be then be used as a function, for a type, or an expression.
668
669Haskell supports pattern matching as the main way to extract values from a
670tuple, although helper functions "fst" and "snd" are provided for field
671access on two element tuples.
672
673Haskell does not have 0 or 1-element tuples. The nil type, written "()" for
674both type and value, is effectively the 0 element tuple. There is also a type
675called "Solo" that is a polymorphic structure with one field and is used as
676the 1-element tuple, but has regular Haskell data-type syntax.
677
678- https://www.haskell.org/onlinereport/basic.html
679
680#### OCaml
681
682OCaml only supports multi-element (2 or more) tuples. It does have the `unit`
683type, which has one value, written `()`. Tuple types are written as a '*'
684separated list of types, tuple values are written as ',' separated list of
685expressions. Pattern matching tuples is supported and uses the same syntax as
686values. Parenthesizing these lists is only needed for grouping.
687
688- https://ocaml.org/docs/basic-data-types#tuples
689
690#### Swift
691
692Swift has tuple types that use the basic parenthesized, comma separated list of
693types or values. It only supports 0 and 2 or more element tuples (the `Void`
694type is an alias for the empty tuple type).
695
696Swift also supports named tuples. Names can be added before the tuple element,
697both for the tuple type and value. The syntax is a name followed by a colon,
698e.g., `(first: int, second: int)`. These names are a fixed part of the type,
699and can be used as part of field access notation (otherwise numbers are used
700in-place of field names `tuple.0` vs. `tuple.first`).
701
702- https://docs.swift.org/swift-book/documentation/the-swift-programming-language/types/#Tuple-Type
703
704#### Python
705
706In Python tuples are immutable lists. Because they are dynamically typed,
707there is only one tuple type `tuple`.
708
709It also has various named tuples. The first, namedtuple, allows naming the
710elements of the tuple. The second, NamedTuple, is actually a way of creating a
711typed record in a normally untyped language.
712
713- https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
714- https://docs.python.org/3/library/collections.html#collections.namedtuple
715- https://docs.python.org/3/library/typing.html#typing.NamedTuple
716
717#### LISP
718As LISP is dynamically typed, its `cons` data type is an untyped pair and is
719(perhaps infamously) the main constructor of all compound data types. The
720function `cons` takes two arguments and builds a pair. Functions `car` and
721`cdr` get the first and second elements of the pair.
722
723### Packs
724Packs (or unstructured tuples) are a much less common feature. In fact, there
725might just be one language, C++, that supports packs. The most common use
726case for unstructured tuples is returning multiple values, so there is a
727comparison to languages that have that as a special feature.
728
729#### Go
730Go does not have built in tuple types, but it has multi-return syntax that
731looks like the tuple syntax of many other languages.
732
733```
734func main() {
735 i, j := returnIntInt()
736 ...
737}
738
739func returnIntInt() (int, int) {
740 return 12, 34
741}
742```
743
744- https://golangdocs.com/functions-in-golang
745- https://go.dev/src/go/types/tuple.go
746
747#### Lua
748Lua is a scripting language that is dynamically typed and stack based. Although
749the stack is usually only directly visible in the C-API, it does allow any
750function to return any number of values, even a single return, in the return
751expression
752
753```
754local funcion f()
755 return 12, 34
756end
757
758local i, j = f()
759```
Note: See TracBrowser for help on using the repository browser.