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