| [7a0e8c8] | 1 | Tuples | 
|---|
|  | 2 | ====== | 
|---|
|  | 3 |  | 
|---|
| [12c4a5f] | 4 | This is a proposal discusses update to Cforall tuples as they exist after | 
|---|
|  | 5 | the work done by Robert Schluntz (after they were added to Cforall by | 
|---|
|  | 6 | Rodolfo Esteves and to K-W C by Dave Till). The new features and ideas | 
|---|
|  | 7 | discussed here are designed to address problems that have appeared as tuples | 
|---|
|  | 8 | have been used in the past 6 years. Some or all of the changes discussed may | 
|---|
|  | 9 | already be implemented. | 
|---|
| [7a0e8c8] | 10 |  | 
|---|
| [1b770e40] | 11 | The core change is breaking the current restructurable tuples into unstructured | 
|---|
|  | 12 | and structured tuples. Unstructured tuples handle most of the existing uses, | 
|---|
|  | 13 | with structured tuples filling in a few missing use cases. | 
|---|
| [7a0e8c8] | 14 |  | 
|---|
|  | 15 | Current State of Tuples | 
|---|
|  | 16 | ----------------------- | 
|---|
| [12c4a5f] | 17 | An overview of the current tuple design is the starting place for the proposal. | 
|---|
| [7a0e8c8] | 18 |  | 
|---|
|  | 19 | ### Tuple Syntax | 
|---|
|  | 20 |  | 
|---|
| [1b770e40] | 21 | Currently, tuples have three main components: tuple types, tuple | 
|---|
|  | 22 | expressions/values (constructing tuples), and tuple index expressions | 
|---|
|  | 23 | (deconstructing tuples). | 
|---|
|  | 24 |  | 
|---|
| [12c4a5f] | 25 | Current syntax for tuple types: | 
|---|
| [7a0e8c8] | 26 |  | 
|---|
|  | 27 | -   Nullary: [void] or [] | 
|---|
| [12c4a5f] | 28 |  | 
|---|
|  | 29 | [void] f(); | 
|---|
|  | 30 | [] f(); | 
|---|
|  | 31 |  | 
|---|
| [7a0e8c8] | 32 | -   Unary: [TYPE] | 
|---|
| [1b770e40] | 33 |  | 
|---|
| [12c4a5f] | 34 | [int] f(); | 
|---|
|  | 35 | [double] x = 1.5; | 
|---|
| [1b770e40] | 36 |  | 
|---|
| [12c4a5f] | 37 | -   Multiary: [TYPE, TYPE, ...] | 
|---|
| [1b770e40] | 38 |  | 
|---|
| [12c4a5f] | 39 | [bool, char] f(); | 
|---|
|  | 40 | [short, int, long] x; | 
|---|
| [1b770e40] | 41 |  | 
|---|
| [12c4a5f] | 42 | Tuple types can appear anywhere regular types may be used, except for the | 
|---|
|  | 43 | nullary tuple which can only appear where void can be used (usually return | 
|---|
|  | 44 | types), with the exception of special case when void is used to make an empty | 
|---|
|  | 45 | parameter list. | 
|---|
| [7a0e8c8] | 46 |  | 
|---|
|  | 47 | Current syntax for tuple expressions: | 
|---|
|  | 48 |  | 
|---|
| [1b770e40] | 49 | -   Nullary: (Same as `void`, use return without an expression.) | 
|---|
|  | 50 |  | 
|---|
| [12c4a5f] | 51 | [] f() { return; } | 
|---|
| [1b770e40] | 52 |  | 
|---|
| [12c4a5f] | 53 | -   Unary: EXPR (in return only) or {EXPR} | 
|---|
| [1b770e40] | 54 |  | 
|---|
| [12c4a5f] | 55 | [int] f() { return 3; } | 
|---|
|  | 56 | [int] x = {3}; | 
|---|
| [1b770e40] | 57 |  | 
|---|
| [12c4a5f] | 58 | -   Multiary: [EXPR, EXPR, ...] | 
|---|
| [7a0e8c8] | 59 |  | 
|---|
| [12c4a5f] | 60 | [int, int] f() { return [3, 4]; } | 
|---|
|  | 61 | [bool, char, int] x = [true, 'a', 3]; | 
|---|
| [1b770e40] | 62 |  | 
|---|
| [12c4a5f] | 63 | Tuple expressions should be useable whenever an expression of that type is | 
|---|
|  | 64 | expected. Issues with the current state will be discussed later. | 
|---|
| [1b770e40] | 65 |  | 
|---|
|  | 66 | Current syntax for tuple indexing is an integer constant, where its value | 
|---|
|  | 67 | selects a tuple member, e.g.: | 
|---|
| [7a0e8c8] | 68 |  | 
|---|
| [12c4a5f] | 69 | [char, int] tup = ['a', 0]; | 
|---|
|  | 70 | char ch = tup.0; | 
|---|
|  | 71 | int value = tup.1; | 
|---|
| [7a0e8c8] | 72 |  | 
|---|
|  | 73 | ### Mass and Multi-Assignment | 
|---|
|  | 74 |  | 
|---|
| [1b770e40] | 75 | Two special forms of assignment can be used to set values in tuples: mass and | 
|---|
|  | 76 | multi.  Mass assignment assigns every element in the destination tuple to a | 
|---|
|  | 77 | single source value. | 
|---|
| [7a0e8c8] | 78 |  | 
|---|
|  | 79 | [int, long, float] dst; | 
|---|
| [12c4a5f] | 80 | int src = 4; | 
|---|
|  | 81 | dst = src; | 
|---|
|  | 82 | // Becomes: dst.0 = src; dst.1 = src; dst.2 = src; | 
|---|
| [7a0e8c8] | 83 |  | 
|---|
| [1b770e40] | 84 | Multi-assignment assigns every element in the destination tuple to the | 
|---|
|  | 85 | corresponding element in the source tuple. Both tuples must be the same size | 
|---|
| [12c4a5f] | 86 | and the elements pairs must be assignment compatible conversions. | 
|---|
| [7a0e8c8] | 87 |  | 
|---|
| [1b770e40] | 88 | [long, int, float] dst; | 
|---|
|  | 89 | [int, char, double] src = [1, 'a', 300.0]; | 
|---|
| [12c4a5f] | 90 | dst = src; | 
|---|
|  | 91 | // Becomes: dst.0 = src.0; dst.1 = src.1; dst.2 = src.1; | 
|---|
| [7a0e8c8] | 92 |  | 
|---|
|  | 93 | ### Tuple Restructuring | 
|---|
|  | 94 |  | 
|---|
|  | 95 | Tuples can be restructured as part of overload resolution. Function calls | 
|---|
| [1b770e40] | 96 | unpack tuples and repack tuples to match signatures. This semantics is a form | 
|---|
|  | 97 | of implicit conversion and is considered during overload resolution. | 
|---|
| [7a0e8c8] | 98 |  | 
|---|
|  | 99 | A simple example is matching multiple parameters of a function to a single | 
|---|
| [1b770e40] | 100 | argument expression, where each parameter is bound to a different element of | 
|---|
|  | 101 | the returned tuple. | 
|---|
| [7a0e8c8] | 102 |  | 
|---|
| [1b770e40] | 103 | [int, int] argFunc(); | 
|---|
|  | 104 | void parmFunc(int a, int b, int c, int d); | 
|---|
|  | 105 |  | 
|---|
|  | 106 | parmFunc(argFunc(), argFunc()); | 
|---|
| [7a0e8c8] | 107 |  | 
|---|
|  | 108 | // Roughly equivilent to: | 
|---|
| [1b770e40] | 109 | [int, int] fst = argFunc(); | 
|---|
|  | 110 | [int, int] snd = argFunc(); | 
|---|
|  | 111 | parmFunc(fst.0, fst.1, snd.0, snd.1); | 
|---|
| [7a0e8c8] | 112 |  | 
|---|
|  | 113 | ### Tuple Casts | 
|---|
|  | 114 |  | 
|---|
| [b0fcd0e] | 115 | C-style casts can be used on tuples. These are usually conversion casts (casts | 
|---|
| [1b770e40] | 116 | that perform operations on the cast type, as opposed to reinterpreting the | 
|---|
| [7a0e8c8] | 117 | existing value). | 
|---|
|  | 118 |  | 
|---|
|  | 119 | Tuple casts can remove elements from the end of a tuple and apply a recursive | 
|---|
|  | 120 | cast to any of the elements. As an example: | 
|---|
|  | 121 |  | 
|---|
|  | 122 | [char, char, char] x = ['a', 'b', 'c']; | 
|---|
|  | 123 | ([int, char])x; | 
|---|
|  | 124 |  | 
|---|
| [1b770e40] | 125 | This casts the first element type of x from a char to an int and drops the last | 
|---|
| [b0fcd0e] | 126 | element. The second line can be replaced with the following code, which creates | 
|---|
| [1b770e40] | 127 | a new tuple by directly casting the kept elements of the old tuple: | 
|---|
| [7a0e8c8] | 128 |  | 
|---|
|  | 129 | [(int)x.0, (char)x.1]; | 
|---|
|  | 130 |  | 
|---|
|  | 131 | Note, tuple casting is more restricted than the implicit tuple restructuring. | 
|---|
|  | 132 | It cannot do any restructuring beyond dropping the trailing elements of | 
|---|
| [1b770e40] | 133 | tuples. For example, | 
|---|
|  | 134 |  | 
|---|
|  | 135 | int i; char c; bool b; | 
|---|
|  | 136 | [i, b, c, i] = [i, [b, c], i]; | 
|---|
|  | 137 | [int, [bool, char], int] w; | 
|---|
|  | 138 | [i, b, c, i] = w; | 
|---|
| [12c4a5f] | 139 | [i, b, c] = ([int, [bool, char]])w; | 
|---|
|  | 140 | // But this does not work: | 
|---|
|  | 141 | [i, b, c, i] = ([int, bool, char, int])w; | 
|---|
| [7a0e8c8] | 142 |  | 
|---|
|  | 143 | ### Polymorphic Tuple Parameters | 
|---|
|  | 144 |  | 
|---|
|  | 145 | One kind of polymorphic parameter is the tuple type parameter, previously | 
|---|
|  | 146 | noted by the `ttype` keyword and now marked by a tailing ellipses (`...`). | 
|---|
|  | 147 | Although described as a tuple type, it is usually viewed as its flattened | 
|---|
|  | 148 | sequence of types. | 
|---|
|  | 149 |  | 
|---|
| [12c4a5f] | 150 | // Fixed Length Max - Base Case: | 
|---|
|  | 151 | forall(T | { int ?>?( T, T ); }) | 
|---|
|  | 152 | T max(T v1, T v2) { return v1 > v2 ? v1 : v2; } | 
|---|
| [1b770e40] | 153 |  | 
|---|
| [12c4a5f] | 154 | // Variable Length Max - Recursive: | 
|---|
|  | 155 | forall(T, Ts... | { T max(T, T); T max(Ts); }) | 
|---|
| [7a0e8c8] | 156 | T max(T arg, Ts args) { | 
|---|
|  | 157 | return max(arg, max(args)); | 
|---|
|  | 158 | } | 
|---|
|  | 159 |  | 
|---|
| [12c4a5f] | 160 | This feature introduces a type name into scope (Ts). It is used as a type and | 
|---|
|  | 161 | the value it declares (args) is a tuple value, and that the second assertion | 
|---|
|  | 162 | "T max(Ts);" matches takes a single tuple, but with restructuring it can also | 
|---|
|  | 163 | match functions that take a series of non-tuple arguments. | 
|---|
|  | 164 |  | 
|---|
|  | 165 | A good way to explain this is to show a partial implementation the following | 
|---|
|  | 166 | call `max(int_a, int_b, int_c);` into actual code: | 
|---|
|  | 167 |  | 
|---|
|  | 168 | void _thunk0(int v1, int v2) { | 
|---|
|  | 169 | return max{?>?}(v1, v2); | 
|---|
|  | 170 | } | 
|---|
|  | 171 | void _thunk1([int, int] tup) { | 
|---|
|  | 172 | return max{?>?}(tup.0, tup.1); | 
|---|
|  | 173 | } | 
|---|
|  | 174 | max{_thunk0, _thunk1}(int_a, [int_b, int_c]); | 
|---|
|  | 175 |  | 
|---|
|  | 176 | The part to highlight is that "_thunk1", one of the functions created to | 
|---|
|  | 177 | make an inexact match that can be used as a call site into an exact match | 
|---|
|  | 178 | so the function can be passed through function parameters. | 
|---|
|  | 179 | Although, the tuple type `[int, int]` matches a parameter list `int, int`, | 
|---|
|  | 180 | just with a bit of low lever restructuring. | 
|---|
| [1b770e40] | 181 |  | 
|---|
| [12c4a5f] | 182 | In larger cases (with four or more parameters in the first case), the | 
|---|
|  | 183 | recursive case will work similarly, creating an intermidate function to | 
|---|
|  | 184 | restructure the tuple into a new call such as: | 
|---|
|  | 185 |  | 
|---|
|  | 186 | void _thunk2([int, int, int] tup) { | 
|---|
|  | 187 | return max{_thunk0, _thunk1}(tup.0, [tup.1, tup.2]); | 
|---|
|  | 188 | } | 
|---|
| [7a0e8c8] | 189 |  | 
|---|
|  | 190 | Issues with the Current State | 
|---|
|  | 191 | ----------------------------- | 
|---|
| [1b770e40] | 192 | There are a variety of problems with the current implementation which need to | 
|---|
|  | 193 | be fixed. | 
|---|
| [7a0e8c8] | 194 |  | 
|---|
|  | 195 | ### Tuples are not Objects | 
|---|
|  | 196 |  | 
|---|
| [1b770e40] | 197 | Spoilers: this proposal actually takes them even further away from being | 
|---|
|  | 198 | objects, but illustrates why the current version is not as useful is it could | 
|---|
|  | 199 | be. | 
|---|
|  | 200 |  | 
|---|
| [12c4a5f] | 201 | Tuples do not have the lifetime operators (copy construction, copy assignment | 
|---|
|  | 202 | and destructor) that define an object type in Cforall. This means they are | 
|---|
|  | 203 | not polymorphic otypes, they are objects in C's usage of the word. This means | 
|---|
|  | 204 | that tuples cannot be used as a single object in | 
|---|
|  | 205 |  | 
|---|
|  | 206 | There are several reasons for this. The fluid nature of tuples (flattening | 
|---|
|  | 207 | and restructuring) means that matching against a tuple type is fluid in some | 
|---|
|  | 208 | inconvent ways. | 
|---|
| [7a0e8c8] | 209 |  | 
|---|
| [12c4a5f] | 210 | forall(T, U) void ?{}([T, U] & this; | 
|---|
|  | 211 | void ?{}([int, int, int] & this); | 
|---|
|  | 212 | // What constructor(s) should be used here? | 
|---|
|  | 213 | [int, [int, int]] pair; | 
|---|
| [7a0e8c8] | 214 |  | 
|---|
| [12c4a5f] | 215 | Should the polymorpic constructor be applied twice or should the tuple be | 
|---|
|  | 216 | restructured and the three element constructor be used? This is not really | 
|---|
|  | 217 | something a user should be wondering about (especially since tuples should | 
|---|
|  | 218 | always be simple containers wrapping their elements). | 
|---|
| [7a0e8c8] | 219 |  | 
|---|
| [b0fcd0e] | 220 | ### Providing TType Arguments is Inconsistent | 
|---|
| [7a0e8c8] | 221 |  | 
|---|
| [1b770e40] | 222 | The syntax for ttype arguments is slightly inconsistent. It has not come up | 
|---|
|  | 223 | much yet, because you do not directly provide ttype polymorphic arguments to | 
|---|
| [b0fcd0e] | 224 | functions and there are very few existing use-cases for ttype structures. | 
|---|
| [7a0e8c8] | 225 |  | 
|---|
| [12c4a5f] | 226 | forall(T, Us...) | 
|---|
|  | 227 | void function(T head, Us tail) {} | 
|---|
|  | 228 |  | 
|---|
|  | 229 | forall(T, Us...) | 
|---|
|  | 230 | struct Type {}; | 
|---|
|  | 231 |  | 
|---|
|  | 232 | int a;  bool b;  char c; | 
|---|
|  | 233 | function(a, b, c); | 
|---|
|  | 234 | function(a, [b, c]); | 
|---|
|  | 235 | // Doesn't work: Type(int, bool, char) x; | 
|---|
|  | 236 | Type(int, [bool, char]) x; | 
|---|
|  | 237 |  | 
|---|
|  | 238 | The `function` case works either way because the tuple value can be | 
|---|
|  | 239 | restructured if it does not match exactly. The `Type` case though does not | 
|---|
|  | 240 | allow restructuring when the type is passed as a type value. This may be | 
|---|
|  | 241 | the most correct form, but seems surprising compared to the common use. | 
|---|
| [7a0e8c8] | 242 |  | 
|---|
| [5485bcac] | 243 | ### Tuple Arrays Not Supported | 
|---|
|  | 244 |  | 
|---|
|  | 245 | You cannot make an array of tuples. For some reason the type just will not | 
|---|
|  | 246 | work. Thoretically it could, each tuple type has a size and alignment and | 
|---|
|  | 247 | could be put in an array. However the following type is not accepted: | 
|---|
|  | 248 |  | 
|---|
|  | 249 | [int, char] array[3] = {{1, 'a'}, {2, 'b'}, {3, 'c'}}; | 
|---|
|  | 250 |  | 
|---|
|  | 251 | Now the opposite, a tuple containing an array, does actually work: | 
|---|
|  | 252 |  | 
|---|
|  | 253 | [int[3], char] data = {{1, 2, 3}, 'd'}; | 
|---|
|  | 254 |  | 
|---|
| [12c4a5f] | 255 | ### Unary Tuple Value Syntax | 
|---|
|  | 256 |  | 
|---|
|  | 257 | Unary tuples don't use the standard tuple syntax and one of the two alternate | 
|---|
|  | 258 | syntax options your may use instead doesn't work consistently. Solving both | 
|---|
|  | 259 | of these issues would help with consistency. | 
|---|
| [7a0e8c8] | 260 |  | 
|---|
|  | 261 | ### Syntax Conflict | 
|---|
|  | 262 |  | 
|---|
| [12c4a5f] | 263 | The tuple syntax conflicts with designators and the C23 (C++-style) attribute | 
|---|
| [1b770e40] | 264 | syntax. | 
|---|
|  | 265 |  | 
|---|
| [12c4a5f] | 266 | int a[10] = { [2] = 3 }; | 
|---|
|  | 267 |  | 
|---|
|  | 268 | Here [2] = 3 is an array designator, but has the same syntax as an assignment | 
|---|
|  | 269 | to a tuple of references, it isn't until the resolver determains that 2 is | 
|---|
|  | 270 | not a reference that that case can be ruled out. | 
|---|
|  | 271 |  | 
|---|
|  | 272 | [[ unused ]] [[int, int], int] x; | 
|---|
|  | 273 |  | 
|---|
|  | 274 | Here the opening `[[` could be a nested tuple or the beginning of an | 
|---|
|  | 275 | attribute, the parser can't figure out until later. | 
|---|
| [1b770e40] | 276 |  | 
|---|
|  | 277 | These conflicts break C compatibility goals of Cforall. Designators had to | 
|---|
|  | 278 | their syntax change and Cforall cannot parse the new attributes. | 
|---|
| [12c4a5f] | 279 | The behaviour of these cases, if they could be parsed, should be unchanged. | 
|---|
| [7a0e8c8] | 280 |  | 
|---|
|  | 281 | Change in Model | 
|---|
|  | 282 | --------------- | 
|---|
|  | 283 | This proposal modifies the existing tuples to better handle its main use | 
|---|
| [12c4a5f] | 284 | cases. There are some use cases that are no longer handled, so in addition | 
|---|
|  | 285 | to changing the existing "restructured tuples" to "unstructured tuples" | 
|---|
|  | 286 | (or packs) a new "structured tuple" is added in the form of struct tuple. | 
|---|
| [7a0e8c8] | 287 |  | 
|---|
| [12c4a5f] | 288 | The new tuples is even more "unstructured" than before. New tuples are | 
|---|
| [1b770e40] | 289 | considered a sequence of types or typed entities. These packs are then unpacked | 
|---|
|  | 290 | into the surrounding context. Put differently, tuples are now flattened as much | 
|---|
|  | 291 | as possible, with some places (like parameter lists) being treated as an | 
|---|
|  | 292 | implicit tuple and the tuple being flattened into that. | 
|---|
| [7a0e8c8] | 293 |  | 
|---|
| [1b770e40] | 294 | Structured tuples are now a separate feature: a structure called "tuple". | 
|---|
|  | 295 | These are polymorphic structures; an instance should act as a structure, except | 
|---|
|  | 296 | its fields are accessed using indices instead of field names. Experience so far | 
|---|
|  | 297 | is that structured tuples are not used often, but fill in the use cases that | 
|---|
|  | 298 | unstructured tuples no longer support. | 
|---|
| [7a0e8c8] | 299 |  | 
|---|
| [12c4a5f] | 300 | Note that the underlying implementation might not actually look like this, | 
|---|
|  | 301 | but this is how it should seem to the user. | 
|---|
| [7a0e8c8] | 302 |  | 
|---|
|  | 303 | Changed Features | 
|---|
|  | 304 | ---------------- | 
|---|
| [b0fcd0e] | 305 | Some of the concrete changes to the features of the language. | 
|---|
| [7a0e8c8] | 306 |  | 
|---|
| [12c4a5f] | 307 | ### Unstructured Tuple / Pack Type | 
|---|
|  | 308 |  | 
|---|
|  | 309 | The evolution of our current tuples (called packs in an attempt to make the | 
|---|
|  | 310 | names more distinct). They work like the current tuples except they are | 
|---|
|  | 311 | always flattened. You might still be able to declare nested tuples, but these | 
|---|
|  | 312 | are not meaningful, they are flattened to a single level and, where | 
|---|
|  | 313 | approprate, is inlined into the sounding content. | 
|---|
|  | 314 |  | 
|---|
|  | 315 | [bool, [char, int], long] x; | 
|---|
|  | 316 | // Becomes: | 
|---|
|  | 317 | [bool, char, int, long] x; | 
|---|
|  | 318 |  | 
|---|
|  | 319 | void f(bool a0, [char, [int, long]] a1, float a2); | 
|---|
|  | 320 | // Becomes: | 
|---|
|  | 321 | void f(bool a0, char a1_0, int a1_1, long a1_2, float a2); | 
|---|
|  | 322 |  | 
|---|
|  | 323 | [,] f(int a0, [,] a1, [bool,] a2); | 
|---|
|  | 324 | // Becomes: | 
|---|
|  | 325 | void f(int a0, bool a2); | 
|---|
|  | 326 |  | 
|---|
|  | 327 | This actually means that tuples do not participate in overload resolution | 
|---|
|  | 328 | in the way the do currently. Restructuring is always free because they are | 
|---|
|  | 329 | always reduced to the same form as part of type matching. | 
|---|
|  | 330 |  | 
|---|
|  | 331 | Packs are still not object types and do not have lifetime functions. They | 
|---|
|  | 332 | cannot be used as object types, nor as any data type, but they can be still | 
|---|
|  | 333 | used as ttypes. | 
|---|
|  | 334 |  | 
|---|
|  | 335 | (Unless they need to have lifetime functions for other features.) | 
|---|
|  | 336 |  | 
|---|
| [7a0e8c8] | 337 | ### Structured Tuple Type | 
|---|
|  | 338 |  | 
|---|
| [12c4a5f] | 339 | There is a standard library or built-in type named `tuple`, it does not need | 
|---|
|  | 340 | a special syntax to write types or values. The type definition might need | 
|---|
|  | 341 | some primitive support, but if supported as a regular type would look | 
|---|
|  | 342 | something like this: | 
|---|
| [7a0e8c8] | 343 |  | 
|---|
|  | 344 | forall(Ts...) | 
|---|
|  | 345 | struct tuple { | 
|---|
| [12c4a5f] | 346 | inline Ts all; | 
|---|
| [7a0e8c8] | 347 | }; | 
|---|
|  | 348 |  | 
|---|
| [12c4a5f] | 349 | This type wraps up an unstructured tuple, in this case a pack of members, | 
|---|
|  | 350 | and gives it "edges" and so structure. It also gives tuples an interface | 
|---|
|  | 351 | more like a normal structure type, for the anonymous structure use case. | 
|---|
|  | 352 |  | 
|---|
|  | 353 | The struct tuple does have lifetime functions, can use list initializer | 
|---|
|  | 354 | syntax and otherwise be treated as a normal structure. You can use regular | 
|---|
|  | 355 | member access syntax to convert the struct tuple into a unstructured pack, | 
|---|
|  | 356 | writing `object.all`. | 
|---|
|  | 357 |  | 
|---|
|  | 358 | The `inline` modifier is a new specialized feature to allow you use tuple | 
|---|
|  | 359 | index expressions directly on the type. With or without inline you should | 
|---|
|  | 360 | be able to chain a tuple index expression onto the member expression, such | 
|---|
|  | 361 | as `object.all.1`. The inline specifier allows you to skip the middle | 
|---|
|  | 362 | and use `object.1`. | 
|---|
|  | 363 |  | 
|---|
|  | 364 | More complex operations can usually be preformed by taking the pack out of | 
|---|
|  | 365 | the struct tuple and acting on it. Polymorphic operations only have to go one | 
|---|
|  | 366 | level down to get to base operations, there is no need for recursive | 
|---|
|  | 367 | construction, nor manually coding different sizes of tuple. This should give | 
|---|
|  | 368 | most of the ease of working with a primitive tuple, because effectively you | 
|---|
|  | 369 | are except it has been given fixed edges. | 
|---|
|  | 370 |  | 
|---|
|  | 371 | A tuple struct should also be an object type, and let's you pass multiple | 
|---|
|  | 372 | values through a single polymorphic slot. | 
|---|
|  | 373 |  | 
|---|
|  | 374 | A note on the naming here, because there are more pieces that need names | 
|---|
|  | 375 | instead of symbols. The name `tuple` follows because that is the common name, | 
|---|
|  | 376 | although this means this is now the default tuple in some sense. The name of | 
|---|
|  | 377 | the pack was picked to work with inline and make the difference between `.0` | 
|---|
|  | 378 | and `.all` clear. (If inline doesn't work then `get` might be a better name.) | 
|---|
| [7a0e8c8] | 379 |  | 
|---|
|  | 380 | ### Type Qualifier Distribution | 
|---|
|  | 381 |  | 
|---|
|  | 382 | Because tuples are no longer object types, applying type modifiers to them, | 
|---|
|  | 383 | such as cv-qualifiers and pointers, no longer makes sense. That syntax is | 
|---|
|  | 384 | now considered distribution, applying the type modifier to each element of | 
|---|
|  | 385 | the tuple. | 
|---|
|  | 386 |  | 
|---|
| [1b770e40] | 387 | Previously `const [int, bool] &` would mean a const reference to a tuple of an | 
|---|
|  | 388 | integer and a boolean. Now it means an alias for `[const int &, const bool &]`, | 
|---|
|  | 389 | a tuple of a reference to a constant integer and a reference to a constant | 
|---|
|  | 390 | boolean. This also applies to polymorphic tuple type packs `Ts &` in | 
|---|
|  | 391 | polymorphic functions. | 
|---|
| [7a0e8c8] | 392 |  | 
|---|
| [1b770e40] | 393 | This new approach can specify restrictions on tuple variables as for a single | 
|---|
|  | 394 | type variable. For example, this approach can replace the special cased tuple | 
|---|
|  | 395 | operations multi-assignment (N-to-N) and mass-assignment (1-to-N). | 
|---|
| [7a0e8c8] | 396 |  | 
|---|
|  | 397 | // Multi-Assignment | 
|---|
|  | 398 | forall(T, Us... | | 
|---|
|  | 399 | { T & ?=?(T &, T const &); Us & ?=?(Us &, Us const &); }) | 
|---|
|  | 400 | [T, Us] & ?=?(T & dst, Us & dsts, T const & src, Us const & srcs) { | 
|---|
|  | 401 | dst = src; | 
|---|
|  | 402 | dsts = srcs; | 
|---|
|  | 403 | return [dst, dsts]; | 
|---|
|  | 404 | } | 
|---|
|  | 405 |  | 
|---|
|  | 406 | // Mass-Assignment | 
|---|
|  | 407 | forall(T, U, Vs... | | 
|---|
|  | 408 | { U & ?=?(U &, T const &); Vs & ?=?(Vs &, T const &); }) | 
|---|
|  | 409 | [U, Vs] & ?=?(U & dst, Vs & dsts, T const & src) { | 
|---|
|  | 410 | dst = src; | 
|---|
|  | 411 | dsts = src; | 
|---|
|  | 412 | return [dst, dsts]; | 
|---|
|  | 413 | } | 
|---|
|  | 414 |  | 
|---|
|  | 415 | These may not work exactly as given (for one, the copy assignment assertion | 
|---|
| [b0fcd0e] | 416 | in the first function would likely be redundant/conflicting with the implicit | 
|---|
| [7a0e8c8] | 417 | assertions on the parameters), but they show the pattern. Multi-assignment | 
|---|
| [b0fcd0e] | 418 | also would be very hard to write with simple tuple types, because the | 
|---|
| [7a0e8c8] | 419 | relationship between the two halves of the parameter list does have to line | 
|---|
|  | 420 | up, that cannot be enforced with two different tuples. | 
|---|
|  | 421 |  | 
|---|
| [12c4a5f] | 422 | This example doesn't have to be implemented this way because we do have the | 
|---|
|  | 423 | special case operations for these already. | 
|---|
|  | 424 |  | 
|---|
| [7a0e8c8] | 425 | ### Type Packs | 
|---|
|  | 426 |  | 
|---|
| [1b770e40] | 427 | This approach is not a new feature, but a reframing/extension of existing tuple | 
|---|
|  | 428 | tuple polymorphic parameters as polymorphic type packs. The old `Vars...` | 
|---|
|  | 429 | syntax introduces a pack of types into scope. It can be used in much the same | 
|---|
|  | 430 | way as a tuple, but in some new ways to. | 
|---|
| [7a0e8c8] | 431 |  | 
|---|
| [1b770e40] | 432 | The primary existing use remains: to use a polymorphic pack in a parameter | 
|---|
|  | 433 | list, both as part of an assertion and in the signature of the main | 
|---|
|  | 434 | function. The difference is that this is not an enclosed tuple, but a series of | 
|---|
|  | 435 | types. The only effective difference this makes is it does not prefer to match | 
|---|
|  | 436 | another tuple/pack. | 
|---|
| [7a0e8c8] | 437 |  | 
|---|
|  | 438 | This pattern continues to a parameter defined with a pack of types, which | 
|---|
| [b0fcd0e] | 439 | is considered a pack of parameters, and the name it introduces is a pack | 
|---|
| [7a0e8c8] | 440 | of variables, or a flattened tuple. | 
|---|
|  | 441 |  | 
|---|
|  | 442 | forall(Params...) | 
|---|
|  | 443 | void function(Params values); | 
|---|
|  | 444 |  | 
|---|
| [1b770e40] | 445 | New use cases include declarations of members and variables. For example, the | 
|---|
|  | 446 | creation of a structured tuple structure: | 
|---|
| [7a0e8c8] | 447 |  | 
|---|
|  | 448 | forall(Fields...) | 
|---|
|  | 449 | struct tuple { | 
|---|
|  | 450 | Fields get; | 
|---|
|  | 451 | }; | 
|---|
|  | 452 |  | 
|---|
|  | 453 | This is again, treated as a pack of members. They have the same layout as | 
|---|
| [b0fcd0e] | 454 | if they were written out by hand. Now, the name get is still accessed as if | 
|---|
| [7a0e8c8] | 455 | it was a regular, singular, member. The result of that expression is a | 
|---|
|  | 456 | pack expression, a tuple of all the field accesses, which can be used with a | 
|---|
|  | 457 | tuple index expression to access the underlying members. For example: | 
|---|
|  | 458 |  | 
|---|
|  | 459 | tuple(bool, char, int) data = { ... }; | 
|---|
|  | 460 | // This expression: | 
|---|
|  | 461 | data.get.2; | 
|---|
|  | 462 | // Is the same as (if the fields had these names): | 
|---|
|  | 463 | data.[__anonymous0, __anonymous1, __anonymous2].2; | 
|---|
|  | 464 |  | 
|---|
|  | 465 | For local declarations it works similarly, except the name introduced is | 
|---|
| [12c4a5f] | 466 | directly usable as a tuple instead of going through a member access. | 
|---|
| [7a0e8c8] | 467 |  | 
|---|
|  | 468 | forall(Objects...) | 
|---|
|  | 469 | void function( ??? ) { | 
|---|
|  | 470 | ??? | 
|---|
|  | 471 | Objects objs = ???; | 
|---|
|  | 472 | ??? | 
|---|
|  | 473 | } | 
|---|
|  | 474 |  | 
|---|
| [12c4a5f] | 475 | ### Tuple Pack Restructuring | 
|---|
|  | 476 |  | 
|---|
|  | 477 | Implicit restructuring still occurs, but is not considered as part of | 
|---|
|  | 478 | overload resolution. The structure of tuples is ignored, where individual | 
|---|
|  | 479 | tuples begin or end is not considered, just the sequence of types. | 
|---|
|  | 480 |  | 
|---|
|  | 481 | This can be viewed as flattening both sides (the arguments/caller and the | 
|---|
|  | 482 | parameters/callee) before type resolution. Both any nested packs and packs | 
|---|
|  | 483 | into any larger context: | 
|---|
|  | 484 |  | 
|---|
|  | 485 | call(false, ['a', [-7,]], 3.14) | 
|---|
|  | 486 | // Inline Nesting: | 
|---|
|  | 487 | call(false, ['a', -7], 3.14) | 
|---|
|  | 488 | // Inline into Context: | 
|---|
|  | 489 | call(false, 'a', -7, 3.14) | 
|---|
|  | 490 |  | 
|---|
|  | 491 | The main context where tuples are inlined are parameter lists, in that you | 
|---|
|  | 492 | can consider a parameter list as a single pack. Return types could be viewed | 
|---|
|  | 493 | in this way, but because they are presented in a single type, they are | 
|---|
|  | 494 | already wrapped in a tuple. | 
|---|
|  | 495 |  | 
|---|
|  | 496 | [int, [bool, char]] f(); | 
|---|
|  | 497 | // Inline Nesting: | 
|---|
|  | 498 | [int, bool, char] f(); | 
|---|
|  | 499 |  | 
|---|
|  | 500 | This also happens "after" any polymorphic parameters are instantiated. | 
|---|
|  | 501 |  | 
|---|
|  | 502 | Empty packs are also collapsed, logically being removed or replaced with | 
|---|
|  | 503 | void as in the following example: | 
|---|
|  | 504 |  | 
|---|
|  | 505 | // For example, this function: | 
|---|
|  | 506 | forall(Ts...) Ts example(int first, Ts middle, int last); | 
|---|
|  | 507 | // With Ts = [,], should not be treated as: | 
|---|
|  | 508 | [,] example(int first, [,] middle, int last); | 
|---|
|  | 509 | // But instead: | 
|---|
|  | 510 | void example(int first, int last); | 
|---|
|  | 511 |  | 
|---|
|  | 512 | Or closer to it, there are some slight differences between the nullary tuple | 
|---|
|  | 513 | and void, for instance creating some instances for consistency with the other | 
|---|
|  | 514 | tuple types. | 
|---|
|  | 515 |  | 
|---|
|  | 516 | The struct tuple is never implicitly restructured. | 
|---|
|  | 517 |  | 
|---|
| [7a0e8c8] | 518 | ### Tuple Declaration/Deconstruction | 
|---|
|  | 519 |  | 
|---|
|  | 520 | Declaring a tuple acts as a pack of variable declarations. When this is done | 
|---|
| [1b770e40] | 521 | with a written out type (as opposed to a polymorphic parameter above), then the | 
|---|
| [7a0e8c8] | 522 | elements of the tuple can be named. | 
|---|
|  | 523 |  | 
|---|
| [12c4a5f] | 524 | [int quo, int rem] quorem = divmod(a, b); | 
|---|
| [7a0e8c8] | 525 |  | 
|---|
|  | 526 | Here `ret` refers to the tuple, the entire pack, while `quo` and `rem` | 
|---|
|  | 527 | give explicit names to elements of the tuple. Not all the names have to be | 
|---|
|  | 528 | provided, at the very least, any element name can be omitted if the pack name | 
|---|
|  | 529 | is provided, and the pack name can be omitted if all the element names are | 
|---|
|  | 530 | provided. That would ensure every element can be accessed, but it could be | 
|---|
|  | 531 | reduced even more, effectively immediately dropping some values if there is | 
|---|
|  | 532 | no named to access it. | 
|---|
|  | 533 |  | 
|---|
| [12c4a5f] | 534 | This does not add any new functionality, more it is a qualify of life feature | 
|---|
|  | 535 | making it very easy to give elements of a tuple descriptive names | 
|---|
|  | 536 | instead of trying to combine a name for the whole tuple with an index. | 
|---|
|  | 537 | (`quo` and `rem` vs. `quorem.0` and `quorem.1`) | 
|---|
| [1b770e40] | 538 |  | 
|---|
| [7a0e8c8] | 539 | ### Tuple Casts | 
|---|
|  | 540 |  | 
|---|
|  | 541 | Tuple casts are no longer allowed to do any restructuring. Internal | 
|---|
|  | 542 | restructuring would not be useful, as there is no internal structure. | 
|---|
|  | 543 | Dropping tail elements could be added back in but it is a narrow use case | 
|---|
|  | 544 | so it may be replaced with other features (for example: tuple slicing). | 
|---|
|  | 545 |  | 
|---|
| [12c4a5f] | 546 | // Allowed: | 
|---|
|  | 547 | ([bool, short])tuple_bool_char; | 
|---|
|  | 548 | ([int, float])tuple_float_float; | 
|---|
| [7a0e8c8] | 549 |  | 
|---|
| [12c4a5f] | 550 | // Forbidden: | 
|---|
|  | 551 | ([int, int, int])tuple_int_int; | 
|---|
|  | 552 | ([long, [float, int]])tuple_long_float_int; | 
|---|
| [7a0e8c8] | 553 |  | 
|---|
| [5485bcac] | 554 | ### Tuple Array Support | 
|---|
|  | 555 |  | 
|---|
|  | 556 | Arrays of unstructured tuples/packs are not actually required, an array of | 
|---|
|  | 557 | structures will store the data just as well and this is not the transient | 
|---|
|  | 558 | context where the unlabeled tuples work well. So it would work fine to | 
|---|
|  | 559 | not allow an array of packs, but if we do not a better error should be | 
|---|
|  | 560 | created. | 
|---|
|  | 561 |  | 
|---|
|  | 562 | All that being said, the following could just work. | 
|---|
|  | 563 |  | 
|---|
|  | 564 | [int, char] u_array[3] = {...}; | 
|---|
|  | 565 |  | 
|---|
|  | 566 | What should definitely work is an array of struct tuple, because that should | 
|---|
|  | 567 | work where a generic structure could be used. This is also part of why arrays | 
|---|
|  | 568 | of packs are not needed. | 
|---|
|  | 569 |  | 
|---|
|  | 570 | tuple([int, char]) s_array[3] = {...}; | 
|---|
|  | 571 |  | 
|---|
| [12c4a5f] | 572 | ### New Tuple Literal Syntax | 
|---|
| [7a0e8c8] | 573 |  | 
|---|
| [12c4a5f] | 574 | In addition to the main redesign of this proposal, the syntax is updated to | 
|---|
|  | 575 | be more consistant. It is now a "[]" list with "," separators and a ","/comma | 
|---|
|  | 576 | terminator that is optional for tuples with 2 or more elements (those that | 
|---|
|  | 577 | already have a comma in them) and manditory in other tuples. | 
|---|
| [7a0e8c8] | 578 |  | 
|---|
| [12c4a5f] | 579 | It should also be symmetric between types and value, differing only in that | 
|---|
|  | 580 | an element (ITEM below) of a tuple type is a type and the element of a tuple | 
|---|
|  | 581 | value is a value. | 
|---|
| [7a0e8c8] | 582 |  | 
|---|
| [12c4a5f] | 583 | -   Nullary Tuple: [,] | 
|---|
|  | 584 | -   Unary Tuples: [ITEM,] | 
|---|
|  | 585 | -   Multiary Tuples: [ITEM, ITEM] [ITEM, ITEM,] [ITEM, ITEM, ITEM] ... | 
|---|
| [7a0e8c8] | 586 |  | 
|---|
| [12c4a5f] | 587 | Currently, although syntax is provided to write them, but manually writing | 
|---|
|  | 588 | a nullary or unary tuple should be very rare. This is because a nullary tuple | 
|---|
|  | 589 | can either be erased or treated as void, while unary tuples can be treated | 
|---|
|  | 590 | as their element type. Similary for nested tuples, can be treated as the | 
|---|
|  | 591 | flattened tuple. | 
|---|
| [7a0e8c8] | 592 |  | 
|---|
| [12c4a5f] | 593 | (Update: Orignal nullary tuple proposal was `[]`, but that had some issues.) | 
|---|
| [7a0e8c8] | 594 |  | 
|---|
| [12c4a5f] | 595 | ### TType Argument Syntax | 
|---|
|  | 596 |  | 
|---|
|  | 597 | It would be nice to seamlessly provide packs of types in polymorphic | 
|---|
|  | 598 | argument lists, however doing so requires a new restriction. | 
|---|
|  | 599 |  | 
|---|
|  | 600 | As an example of the change, consider providing arguments to a polymorphic | 
|---|
|  | 601 | structure with a otype and a ttype parameter. | 
|---|
| [7a0e8c8] | 602 |  | 
|---|
| [12c4a5f] | 603 | forall(T, Us...) struct D {...}; | 
|---|
|  | 604 | D(int, [,])            // D(int) | 
|---|
|  | 605 | D(bool, [char,])       // D(bool, char) | 
|---|
|  | 606 | D(long, [int, short])  // D(long, int, short) | 
|---|
|  | 607 |  | 
|---|
|  | 608 | This is a nice syntaxtic improvement for the common case, it does restrict | 
|---|
|  | 609 | some of the more complex cases, such as cases with two ttypes. There are | 
|---|
|  | 610 | various restrictions on this for functions. If the rules make it unambigous | 
|---|
|  | 611 | then the grouping could be omitted, or a more advanced version, it can be | 
|---|
|  | 612 | ommited only in the indivual cases where it is unambigous. | 
|---|
| [7a0e8c8] | 613 |  | 
|---|
|  | 614 | ### Tuple Slicing (Range Tuple Indexing) | 
|---|
|  | 615 |  | 
|---|
|  | 616 | (Disclaimer: this is a bit more impulsive, see end for notes.) | 
|---|
|  | 617 |  | 
|---|
| [1b770e40] | 618 | This is an extension to tuple indexing. Currently, only single location of a | 
|---|
|  | 619 | tuple can be index, extracting the element at that location. By extending the | 
|---|
|  | 620 | index expression to be a list-range a multiple sub-tuples can be extracted. | 
|---|
|  | 621 |  | 
|---|
| [12c4a5f] | 622 | ['a', 'b', 'c', 'd', 'e', 'f'].0~3 | 
|---|
|  | 623 | ['a', 'b', 'c', 'd', 'e', 'f'].1~5~2 | 
|---|
|  | 624 | // Produces: | 
|---|
|  | 625 | ['a', 'b', 'c'] | 
|---|
|  | 626 | ['b', 'd'] | 
|---|
| [7a0e8c8] | 627 |  | 
|---|
| [12c4a5f] | 628 | In words, the first indexes the first 3 tuple elements (0, 1, 2, stopping | 
|---|
|  | 629 | before index 3) and the second indexes elements starting at index 1, stoping | 
|---|
|  | 630 | before index 5 going in steps of 2. This does not allow for arbitary | 
|---|
|  | 631 | selections, but it covers many common slices. | 
|---|
| [1b770e40] | 632 |  | 
|---|
|  | 633 | To get the new tuple, the range has to be constructed and traversed at compile | 
|---|
|  | 634 | time. The size of the range is the size of the result tuple, with each element | 
|---|
|  | 635 | in the result tuple decided by the matching element of the range, which gives | 
|---|
|  | 636 | the index of the original tuple to place there.  The type of the indices may be | 
|---|
|  | 637 | an integral type, but all indices must be in range, otherwise it is a compile | 
|---|
|  | 638 | time error. | 
|---|
| [7a0e8c8] | 639 |  | 
|---|
|  | 640 | In terms of the existing range loops, if you could write this dynamically, it | 
|---|
|  | 641 | would look something like this: | 
|---|
|  | 642 |  | 
|---|
|  | 643 | // Implementation of: output = input.RANGE | 
|---|
|  | 644 | dyn output = [] | 
|---|
|  | 645 | for ( index : RANGE ) { output = [output, input.index] } | 
|---|
|  | 646 |  | 
|---|
|  | 647 | The result of the expression is only has to be considered a tuple if the | 
|---|
|  | 648 | range has two or more values, otherwise it can be considered void or the same | 
|---|
|  | 649 | as indexing the single value in the range. | 
|---|
|  | 650 |  | 
|---|
| [1b770e40] | 651 | Some closing notes, this is dependent on generalized range expressions. | 
|---|
| [7a0e8c8] | 652 | The iterators proposal has a section on some new range types, this feature | 
|---|
|  | 653 | is intended to be built on that feature. Not simply reuse some of the syntax | 
|---|
| [12c4a5f] | 654 | that is also used in the special for loop. This may add some requirements | 
|---|
|  | 655 | about the compile-time evaluation on range expressions to make sure these | 
|---|
|  | 656 | expressions can be evaluated at compile-time. | 
|---|
| [1b770e40] | 657 |  | 
|---|
| [7a0e8c8] | 658 | Implementation | 
|---|
|  | 659 | -------------- | 
|---|
|  | 660 | An overview of the implementation details of the new proposal. | 
|---|
|  | 661 |  | 
|---|
| [12c4a5f] | 662 | ### Structured Tuple | 
|---|
|  | 663 |  | 
|---|
|  | 664 | There is actually a decision point here. If it can be implemented entirely | 
|---|
|  | 665 | in the standard library, that would be good. If it cannot, it can actually | 
|---|
|  | 666 | be implemented as the same primitive implementation as we use now and may be | 
|---|
|  | 667 | how unstructured tuple packs are implemented. | 
|---|
|  | 668 |  | 
|---|
|  | 669 | ### Member Pack Implementation | 
|---|
|  | 670 |  | 
|---|
|  | 671 | Implementing member packs can use the same pattern as current built-in tuple | 
|---|
|  | 672 | syntax. This is a two stage process, first is by ... then the it can be | 
|---|
|  | 673 | handled by the existing code for polymorphic but not variadic structures. | 
|---|
|  | 674 |  | 
|---|
|  | 675 | forall(Ts...) | 
|---|
|  | 676 | struct tuple { | 
|---|
|  | 677 | Ts get; | 
|---|
|  | 678 | }; | 
|---|
| [1b770e40] | 679 |  | 
|---|
| [12c4a5f] | 680 | So when used with diffences length an hidden declaration is created which | 
|---|
|  | 681 | has the pack parameter replaced with the approprate number of object type | 
|---|
|  | 682 | parameters. Such as in the following examples: | 
|---|
| [1b770e40] | 683 |  | 
|---|
| [12c4a5f] | 684 | struct tuple$0 { | 
|---|
|  | 685 | }; | 
|---|
| [1b770e40] | 686 |  | 
|---|
| [12c4a5f] | 687 | forall(Ts$0) | 
|---|
|  | 688 | struct tuple$1 { | 
|---|
|  | 689 | Ts$0 get$0; | 
|---|
|  | 690 | }; | 
|---|
| [1b770e40] | 691 |  | 
|---|
| [12c4a5f] | 692 | forall(Ts$0, Ts$1) | 
|---|
|  | 693 | struct tuple$2 { | 
|---|
|  | 694 | Ts$0 get$0; | 
|---|
|  | 695 | Ts$1 get$1; | 
|---|
|  | 696 | }; | 
|---|
| [7a0e8c8] | 697 |  | 
|---|
| [12c4a5f] | 698 | Then it can be handled by the regular boxing code. Detection of these cases | 
|---|
|  | 699 | will have to changed because it is no longer a single case. But the pattern | 
|---|
|  | 700 | does work if the structure has additional fields in it, as they are just | 
|---|
|  | 701 | placed before and after the pack. | 
|---|
|  | 702 |  | 
|---|
|  | 703 | forall(U, Ts...) | 
|---|
|  | 704 | struct blob { | 
|---|
|  | 705 | int header; | 
|---|
|  | 706 | Ts data; | 
|---|
|  | 707 | U footer; | 
|---|
|  | 708 | }; | 
|---|
|  | 709 |  | 
|---|
|  | 710 | forall(U, Ts$0, Ts$1) | 
|---|
|  | 711 | struct blob$2 { | 
|---|
|  | 712 | int header; | 
|---|
|  | 713 | Ts$0 data$0; | 
|---|
|  | 714 | Ts$1 data$1; | 
|---|
|  | 715 | U footer; | 
|---|
|  | 716 | }; | 
|---|
|  | 717 |  | 
|---|
|  | 718 | The `inline` member pack just effects the resolver. It should be expanded out | 
|---|
|  | 719 | to the member access and then the tuple index expression. | 
|---|
|  | 720 |  | 
|---|
|  | 721 | ### Local Declaration Pack | 
|---|
|  | 722 |  | 
|---|
|  | 723 | Packs of local declarations can be handled very differently if they are | 
|---|
|  | 724 | concrete or not. Concrete packs can be writen out in full (even if some | 
|---|
|  | 725 | individual elements are polymorphic) can be inlined directly, expanding out | 
|---|
|  | 726 | into a series of object declarations. | 
|---|
|  | 727 |  | 
|---|
|  | 728 | [A, B] local = func(); | 
|---|
|  | 729 | call(local); | 
|---|
|  | 730 |  | 
|---|
|  | 731 | A local$0; | 
|---|
|  | 732 | B local$1; | 
|---|
|  | 733 | ?{}([&local$0, &local$1], func()); | 
|---|
|  | 734 | call([local$0, local$1]); | 
|---|
|  | 735 |  | 
|---|
|  | 736 | In the polymorphic case though, that entire group needs to be reduced to a | 
|---|
|  | 737 | block that can put into a generic function. Here we can use something like | 
|---|
|  | 738 | the currently existing implementation, where the entire tuple an opaque | 
|---|
|  | 739 | bundle, passed to adapters and thunks for handling. | 
|---|
|  | 740 |  | 
|---|
|  | 741 | This is also how tuple declarations are implemented. In the concrete case the | 
|---|
|  | 742 | element names are real and the pack name is replaced with a tuple expression | 
|---|
|  | 743 | of the elements. In the polymorphic case the pack name is real and the | 
|---|
|  | 744 | element names are replaced with tuple index expressions. | 
|---|
|  | 745 |  | 
|---|
|  | 746 | ### Unstructured Tuple Implementation | 
|---|
|  | 747 |  | 
|---|
|  | 748 | Unstructured tuples have two general implementation stratagies. Either they | 
|---|
|  | 749 | are erased and the packs inlined into their context, or they are wrapped | 
|---|
|  | 750 | up as like a structured tuple, generally using the current implementation. | 
|---|
|  | 751 |  | 
|---|
|  | 752 | For example, if a concrete pack (size and all the types are known), appears | 
|---|
|  | 753 | in a parameter list the pack can be split and inlined into the argument list. | 
|---|
|  | 754 |  | 
|---|
|  | 755 | void function(int a, [bool, char, short] b, float c); | 
|---|
|  | 756 | // Becomes: | 
|---|
|  | 757 | void function(int a, bool b0, char b1, short b2, float c); | 
|---|
|  | 758 |  | 
|---|
|  | 759 | On the other hand, if a polymorphic pack appears in the same place, it will | 
|---|
|  | 760 | have to be wrapped up in a dynamic structure so it different instances can | 
|---|
|  | 761 |  | 
|---|
|  | 762 | forall(Ts...) | 
|---|
|  | 763 | void function(int a, Ts b, float c); | 
|---|
|  | 764 | // Becomes (after the implicit packing and unpacking operations): | 
|---|
|  | 765 | forall(T) | 
|---|
|  | 766 | void function(int a, T* b, float c); | 
|---|
| [7a0e8c8] | 767 |  | 
|---|
|  | 768 | ### AST Updates | 
|---|
| [1b770e40] | 769 |  | 
|---|
| [12c4a5f] | 770 | The AST may already be capable of representing most of this. I have only | 
|---|
|  | 771 | identified one feature that definitely will now work in the current AST, and | 
|---|
|  | 772 | that are the object like tuple declarations with named elements. | 
|---|
|  | 773 |  | 
|---|
|  | 774 | Particularly, an object declaration cannot name elements of the tuple. | 
|---|
|  | 775 | To this end a new node type, `TupleDecl`, | 
|---|
|  | 776 | should be added to handle tuple deconstruction declarations | 
|---|
|  | 777 | (other cases can still be handled with `ObjectDecl`). | 
|---|
| [7a0e8c8] | 778 |  | 
|---|
|  | 779 | This would act much like a `FunctionDecl` except for tuples, narrowing the | 
|---|
|  | 780 | possible types, to `TupleType` instances instead of `FunctionType` instances, | 
|---|
| [1b770e40] | 781 | and storing some additional information. In this case, the names of the | 
|---|
| [7a0e8c8] | 782 | elements of the tuples. | 
|---|
|  | 783 |  | 
|---|
|  | 784 | (I'm not actually going to decide the implementation now, but some early | 
|---|
|  | 785 | examination of the problem suggests that it might be better off wrapping a | 
|---|
|  | 786 | series of `ObjectDecl` rather than just some strings to hold the names.) | 
|---|
|  | 787 |  | 
|---|
|  | 788 | Related Features in Other Languages | 
|---|
|  | 789 | ----------------------------------- | 
|---|
| [12c4a5f] | 790 | Other languages have similar features. Organized by the related feature, | 
|---|
|  | 791 | then language (this does mean we can and do hit languages multiple times). | 
|---|
| [7a0e8c8] | 792 |  | 
|---|
|  | 793 | (In hindsight, I may have gone overboard with the number of examples.) | 
|---|
|  | 794 |  | 
|---|
|  | 795 | ### Structured Tuples | 
|---|
|  | 796 |  | 
|---|
|  | 797 | There are many languages with structured tuples. Usually just called tuples, | 
|---|
|  | 798 | but they usually follow the same rules as a polymorphic anonymous structures, | 
|---|
| [1b770e40] | 799 | that is to say N types are provided to create a new N-arity tuple. They also | 
|---|
| [7a0e8c8] | 800 | usually have some special syntax to index the tuples, because there are no | 
|---|
| [1b770e40] | 801 | field names. | 
|---|
| [7a0e8c8] | 802 |  | 
|---|
|  | 803 | #### Rust | 
|---|
| [1b770e40] | 804 |  | 
|---|
| [7a0e8c8] | 805 | Rust has the standard tuples as a primitive in the language. Each arity of | 
|---|
|  | 806 | tuple is a different polymorphic type. | 
|---|
|  | 807 |  | 
|---|
| [b0fcd0e] | 808 | Tuple types and expressions are written with a parenthesized, comma separated | 
|---|
| [7a0e8c8] | 809 | list of types or expressions. To avoid confusion with the grouping "(...)", | 
|---|
| [b0fcd0e] | 810 | one-element tuples must have a trailing comma (and larger tuples may have a | 
|---|
| [7a0e8c8] | 811 | trailing comma). | 
|---|
|  | 812 |  | 
|---|
|  | 813 | const empty: () = (); | 
|---|
|  | 814 | const single: (usize,) = (12,) | 
|---|
|  | 815 | const double: (usize, isize) = (34, -56); | 
|---|
|  | 816 |  | 
|---|
|  | 817 | Element access uses similar syntax to field access (that is "."), but uses an | 
|---|
| [b0fcd0e] | 818 | integer literal instead of a field name (for example: "pair.1"). Tuples | 
|---|
| [7a0e8c8] | 819 | support pattern matching with similar syntax to their expression form. | 
|---|
|  | 820 |  | 
|---|
|  | 821 | Some tuple features only apply up to 12-arity tuples. | 
|---|
| [1b770e40] | 822 | It is not directly stated, but I believe the difference in the more limited | 
|---|
|  | 823 | features are implemented in the standard library, one for each arity of tuple. | 
|---|
| [7a0e8c8] | 824 |  | 
|---|
|  | 825 | -   https://doc.rust-lang.org/std/primitive.tuple.html | 
|---|
|  | 826 | -   https://doc.rust-lang.org/reference/types/tuple.html | 
|---|
|  | 827 | -   https://doc.rust-lang.org/reference/expressions/tuple-expr.html | 
|---|
|  | 828 |  | 
|---|
| [12c4a5f] | 829 | #### C++ | 
|---|
| [1b770e40] | 830 |  | 
|---|
| [7a0e8c8] | 831 | Implemented as a template type defined in the standard library. No special | 
|---|
|  | 832 | language features exist to support this, due to the power of C++'s template | 
|---|
|  | 833 | meta-programming. | 
|---|
|  | 834 |  | 
|---|
| [b0fcd0e] | 835 | C++ is also one of the few languages with support for variadic polymorphic | 
|---|
| [7a0e8c8] | 836 | types, so there is one template that defines all the types. It is written | 
|---|
|  | 837 | as `std::tuple<TYPE...>`, where "TYPE..." is any number of comma separated | 
|---|
|  | 838 | types. This is the standard notation for template type instances. | 
|---|
|  | 839 |  | 
|---|
| [1b770e40] | 840 | There is no special syntax for member access of a tuple. A template function is | 
|---|
|  | 841 | used, e.g., `std::get<0>( tuple )`. | 
|---|
| [7a0e8c8] | 842 |  | 
|---|
| [12c4a5f] | 843 | C++ is an interesting case because it is one of the few cases where no part | 
|---|
|  | 844 | of the tuple system is built-in. It is entirely created in the standard | 
|---|
|  | 845 | library using templates. This also has massive error messages when something | 
|---|
|  | 846 | goes wrong, hopefully our version of struct tuple will reduce the problem. | 
|---|
| [1b770e40] | 847 |  | 
|---|
| [12c4a5f] | 848 | C++ also has structured binding, a kind of limited pattern matching. In a | 
|---|
|  | 849 | structured binding declaration, you can write an auto typed declaration where | 
|---|
|  | 850 | a list of identifiers in a `[]` replaces the single identifier. | 
|---|
| [1b770e40] | 851 |  | 
|---|
| [12c4a5f] | 852 | auto [first, second] = getSize2Tuple(); | 
|---|
| [1b770e40] | 853 |  | 
|---|
| [12c4a5f] | 854 | The base type has to be `auto` because the base type for each element varies. | 
|---|
|  | 855 | Still, qualifiers (cv and reference) on the auto will be distributed to types | 
|---|
|  | 856 | on the individual members. | 
|---|
| [1b770e40] | 857 |  | 
|---|
| [12c4a5f] | 858 | The type bound to the binding may be an array, a "plain" structure or any | 
|---|
|  | 859 | type that implements the tuple-like interface (`std::tuple_size` and | 
|---|
|  | 860 | `std::tuple_element`). | 
|---|
| [1b770e40] | 861 |  | 
|---|
| [12c4a5f] | 862 | -   https://en.cppreference.com/w/cpp/utility/tuple | 
|---|
|  | 863 | -   https://en.cppreference.com/w/cpp/language/structured_binding | 
|---|
| [1b770e40] | 864 |  | 
|---|
| [7a0e8c8] | 865 | #### Haskell | 
|---|
| [1b770e40] | 866 |  | 
|---|
| [7a0e8c8] | 867 | Haskell has a special syntax for tuples, but otherwise they are completely | 
|---|
|  | 868 | normal polymorphic types. Because of this, tuples have a maximum size. | 
|---|
|  | 869 | Haskell (98) supports tuples of up to 15 elements and the standard library | 
|---|
|  | 870 | has functions for tuples of up to 7 elements. | 
|---|
|  | 871 |  | 
|---|
| [b0fcd0e] | 872 | The syntax for tuples is a comma separated list of elements. Either element | 
|---|
| [7a0e8c8] | 873 | types to create a tuple type, or element values to create a tuple value. The | 
|---|
| [1b770e40] | 874 | context decides among them, such as `(6, "six")` or `(Bool, Char, Int)`. | 
|---|
| [7a0e8c8] | 875 |  | 
|---|
| [1b770e40] | 876 | Also all the elements can be removed, getting an expression like "(,)" or | 
|---|
|  | 877 | "(,,,)", which can be then be used as a function, for a type, or an expression. | 
|---|
| [7a0e8c8] | 878 |  | 
|---|
|  | 879 | Haskell supports pattern matching as the main way to extract values from a | 
|---|
|  | 880 | tuple, although helper functions "fst" and "snd" are provided for field | 
|---|
|  | 881 | access on two element tuples. | 
|---|
|  | 882 |  | 
|---|
|  | 883 | Haskell does not have 0 or 1-element tuples. The nil type, written "()" for | 
|---|
|  | 884 | both type and value, is effectively the 0 element tuple. There is also a type | 
|---|
|  | 885 | called "Solo" that is a polymorphic structure with one field and is used as | 
|---|
| [1b770e40] | 886 | the 1-element tuple, but has regular Haskell data-type syntax. | 
|---|
| [7a0e8c8] | 887 |  | 
|---|
|  | 888 | -   https://www.haskell.org/onlinereport/basic.html | 
|---|
|  | 889 |  | 
|---|
|  | 890 | #### OCaml | 
|---|
|  | 891 |  | 
|---|
| [1b770e40] | 892 | OCaml only supports multi-element (2 or more) tuples.  It does have the `unit` | 
|---|
|  | 893 | type, which has one value, written `()`. Tuple types are written as a '*' | 
|---|
|  | 894 | separated list of types, tuple values are written as ',' separated list of | 
|---|
|  | 895 | expressions. Pattern matching tuples is supported and uses the same syntax as | 
|---|
|  | 896 | values. Parenthesizing these lists is only needed for grouping. | 
|---|
| [7a0e8c8] | 897 |  | 
|---|
|  | 898 | -   https://ocaml.org/docs/basic-data-types#tuples | 
|---|
|  | 899 |  | 
|---|
|  | 900 | #### Swift | 
|---|
|  | 901 |  | 
|---|
| [1b770e40] | 902 | Swift has tuple types that use the basic parenthesized, comma separated list of | 
|---|
|  | 903 | types or values. It only supports 0 and 2 or more element tuples (the `Void` | 
|---|
|  | 904 | type is an alias for the empty tuple type). | 
|---|
|  | 905 |  | 
|---|
|  | 906 | Swift also supports named tuples. Names can be added before the tuple element, | 
|---|
|  | 907 | both for the tuple type and value. The syntax is a name followed by a colon, | 
|---|
|  | 908 | e.g., `(first: int, second: int)`. These names are a fixed part of the type, | 
|---|
|  | 909 | and can be used as part of field access notation (otherwise numbers are used | 
|---|
|  | 910 | in-place of field names `tuple.0` vs. `tuple.first`). | 
|---|
| [7a0e8c8] | 911 |  | 
|---|
|  | 912 | -   https://docs.swift.org/swift-book/documentation/the-swift-programming-language/types/#Tuple-Type | 
|---|
|  | 913 |  | 
|---|
|  | 914 | #### Python | 
|---|
| [1b770e40] | 915 |  | 
|---|
|  | 916 | In Python tuples are immutable lists. Because they are dynamically typed, | 
|---|
| [7a0e8c8] | 917 | there is only one tuple type `tuple`. | 
|---|
|  | 918 |  | 
|---|
| [1b770e40] | 919 | It also has various named tuples. The first, namedtuple, allows naming the | 
|---|
|  | 920 | elements of the tuple. The second, NamedTuple, is actually a way of creating a | 
|---|
|  | 921 | typed record in a normally untyped language. | 
|---|
| [7a0e8c8] | 922 |  | 
|---|
|  | 923 | -   https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range | 
|---|
|  | 924 | -   https://docs.python.org/3/library/collections.html#collections.namedtuple | 
|---|
|  | 925 | -   https://docs.python.org/3/library/typing.html#typing.NamedTuple | 
|---|
|  | 926 |  | 
|---|
|  | 927 | #### LISP | 
|---|
| [1b770e40] | 928 | As LISP is dynamically typed, its `cons` data type is an untyped pair and is | 
|---|
|  | 929 | (perhaps infamously) the main constructor of all compound data types.  The | 
|---|
|  | 930 | function `cons` takes two arguments and builds a pair. Functions `car` and | 
|---|
|  | 931 | `cdr` get the first and second elements of the pair. | 
|---|
| [7a0e8c8] | 932 |  | 
|---|
|  | 933 | ### Packs | 
|---|
| [b0fcd0e] | 934 | Packs (or unstructured tuples) are a much less common feature. In fact, there | 
|---|
| [7a0e8c8] | 935 | might just be one language, C++, that supports packs. The most common use | 
|---|
|  | 936 | case for unstructured tuples is returning multiple values, so there is a | 
|---|
|  | 937 | comparison to languages that have that as a special feature. | 
|---|
|  | 938 |  | 
|---|
|  | 939 | #### Go | 
|---|
|  | 940 | Go does not have built in tuple types, but it has multi-return syntax that | 
|---|
|  | 941 | looks like the tuple syntax of many other languages. | 
|---|
|  | 942 |  | 
|---|
|  | 943 | ``` | 
|---|
|  | 944 | func main() { | 
|---|
|  | 945 | i, j := returnIntInt() | 
|---|
|  | 946 | ... | 
|---|
|  | 947 | } | 
|---|
|  | 948 |  | 
|---|
|  | 949 | func returnIntInt() (int, int) { | 
|---|
|  | 950 | return 12, 34 | 
|---|
|  | 951 | } | 
|---|
|  | 952 | ``` | 
|---|
|  | 953 |  | 
|---|
| [12c4a5f] | 954 | Go can unpack multiple return values and pass them all to a function, but | 
|---|
|  | 955 | the unpacked tuple must match the parameter list exactly. | 
|---|
|  | 956 |  | 
|---|
|  | 957 | ``` | 
|---|
|  | 958 | func acceptIntInt(a int, b int) { | 
|---|
|  | 959 | fmt.Println(a, b) | 
|---|
|  | 960 | } | 
|---|
|  | 961 | ``` | 
|---|
|  | 962 |  | 
|---|
| [7a0e8c8] | 963 | -   https://golangdocs.com/functions-in-golang | 
|---|
|  | 964 | -   https://go.dev/src/go/types/tuple.go | 
|---|
|  | 965 |  | 
|---|
|  | 966 | #### Lua | 
|---|
| [1b770e40] | 967 | Lua is a scripting language that is dynamically typed and stack based. Although | 
|---|
|  | 968 | the stack is usually only directly visible in the C-API, it does allow any | 
|---|
| [12c4a5f] | 969 | function to return any number of values, one, zero or more, from any return | 
|---|
|  | 970 | expression. | 
|---|
| [7a0e8c8] | 971 |  | 
|---|
|  | 972 | ``` | 
|---|
|  | 973 | local funcion f() | 
|---|
|  | 974 | return 12, 34 | 
|---|
|  | 975 | end | 
|---|
|  | 976 |  | 
|---|
|  | 977 | local i, j = f() | 
|---|
|  | 978 | ``` | 
|---|
| [12c4a5f] | 979 |  | 
|---|
|  | 980 | The last argument in an argument list can be an expression - function call - | 
|---|
|  | 981 | with multiple results and all the results are passed to the function after | 
|---|
|  | 982 | other arguments. | 
|---|
|  | 983 |  | 
|---|
|  | 984 | Because Lua is dynamically typed, multiple return values are allowed anywhere | 
|---|
|  | 985 | and the length of the value listed is adjusted, discarding any extra values | 
|---|
|  | 986 | and filling in new values with the value `nil`. This is also used if a | 
|---|
|  | 987 | function is called with the incorrect number of arguments. | 
|---|
|  | 988 |  | 
|---|
|  | 989 | -   https://lua.org/ | 
|---|
|  | 990 | -   https://lua.org/manual/5.4/manual.html#3.4.12 | 
|---|
|  | 991 |  | 
|---|
|  | 992 | #### C++ | 
|---|
|  | 993 |  | 
|---|
|  | 994 | We return to C++ to view template parameter packs. These are the symmetric | 
|---|
|  | 995 | with our pack feature (and both are even used to construct the structured | 
|---|
|  | 996 | tuples in the last section). | 
|---|
|  | 997 |  | 
|---|
|  | 998 | C++ template parameter packs are a modification that can be applied to any | 
|---|
|  | 999 | template parameter, so the parameter now takes a series of arguments instead | 
|---|
|  | 1000 | of just one. These are used in pack expansion, | 
|---|
|  | 1001 | which usually expand to a comma separated list, but it can also be a chain of | 
|---|
|  | 1002 | boolean binary operator applications. For example, if the parameter | 
|---|
|  | 1003 | `typename... Ts` is in scope, then you can declare `std::tuple<Ts...>` to | 
|---|
|  | 1004 | introduce a tuple type, if Ts = bool, char, int then the type becomes | 
|---|
|  | 1005 | `std::tuple<bool, char, int>`. | 
|---|
|  | 1006 |  | 
|---|
|  | 1007 | A common use is for perfect argument forwarding, which shows some different | 
|---|
|  | 1008 | uses of the pattern: | 
|---|
|  | 1009 |  | 
|---|
|  | 1010 | ``` | 
|---|
|  | 1011 | template<typename inner_t> | 
|---|
|  | 1012 | class Outer { | 
|---|
|  | 1013 | inner_t inner; | 
|---|
|  | 1014 | public: | 
|---|
|  | 1015 | template<typename... Args> | 
|---|
|  | 1016 | Outer(Args&&... args) : inner(std::forward<Args>(args)...) {} | 
|---|
|  | 1017 | }; | 
|---|
|  | 1018 | ``` | 
|---|
|  | 1019 |  | 
|---|
|  | 1020 | In the first application, `Args&&... args` both uses a pack and introduces | 
|---|
|  | 1021 | another one. `Arg0&& arg0, Arg1&& arg1, Arg2&& arg2, ...` is the pattern | 
|---|
|  | 1022 | it expands too. The `&&` is used to copy the argument type's reference | 
|---|
|  | 1023 | qualifier (the default can strip references away). | 
|---|
|  | 1024 |  | 
|---|
|  | 1025 | The second application, `std::forward<Args>(args)...` uses two packs. These | 
|---|
|  | 1026 | are expanded in parallel, and must be the same length (in this case they | 
|---|
|  | 1027 | always will be). It also helps show that the `...` is actually the bit doing | 
|---|
|  | 1028 | the expansion, it is a suffix "operator" to the expansion pattern. | 
|---|
|  | 1029 |  | 
|---|
|  | 1030 | There are also fold expressions that use binary operators to combine a pack | 
|---|
|  | 1031 | into a single expression. For example, you can write a variadic sum function | 
|---|
|  | 1032 | that compiles down to the primitive additions. | 
|---|
|  | 1033 |  | 
|---|
|  | 1034 | ``` | 
|---|
|  | 1035 | template<typename... Args> | 
|---|
|  | 1036 | int sum(Args&&... args) { | 
|---|
|  | 1037 | return (args + ... + 0); | 
|---|
|  | 1038 | } | 
|---|
|  | 1039 | ``` | 
|---|
|  | 1040 |  | 
|---|
|  | 1041 | C++ is about the best you could ask for in this area, but it does a lot of work | 
|---|
|  | 1042 | at compile time to make this happen. | 
|---|
|  | 1043 |  | 
|---|
|  | 1044 | -   https://en.cppreference.com/w/cpp/language/template_parameters | 
|---|
|  | 1045 | -   https://en.cppreference.com/w/cpp/language/parameter_pack | 
|---|
|  | 1046 | -   https://en.cppreference.com/w/cpp/language/fold | 
|---|