Changeset 12c4a5f for doc/proposals
- Timestamp:
- Oct 23, 2024, 4:18:15 PM (8 weeks ago)
- Branches:
- master
- Children:
- 90be0cf
- Parents:
- 42d81a7
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/proposals/tuples.md
r42d81a7 r12c4a5f 2 2 ====== 3 3 4 Tuples were introduced by Dave Till in K-W C, added to CFA by Rodolfo Esteves, 5 and extended by Robert Schluntz. This proposal discusses updates for CFA tuples 6 to address problems that have appeared in their usage over the past 6 years. 7 The proposal attempts to address problems by adding new functionality, updating 8 existing features, and removing some problematic ones. 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. 9 10 10 11 The core change is breaking the current restructurable tuples into unstructured … … 14 15 Current State of Tuples 15 16 ----------------------- 16 An overview of the current tuple sdesign is the starting place for the proposal.17 An overview of the current tuple design is the starting place for the proposal. 17 18 18 19 ### Tuple Syntax … … 22 23 (deconstructing tuples). 23 24 24 Current syntax for tuple types .25 Current syntax for tuple types: 25 26 26 27 - Nullary: [void] or [] 28 29 [void] f(); 30 [] f(); 31 27 32 - Unary: [TYPE] 28 - Nary: [TYPE, TYPE, ...] 29 30 Tuple types can appear in a function return and parameter declaration, or a 31 tuple variable declaration. Note, the Nullary tuple is only meaningful in the 32 return context, 33 34 void f(...); // equivalent 35 [void] f(...); 36 [] f(...); 37 38 as C does not support a void declaration. 39 40 int f( void x ); // disallowed 41 int f( [void] x ); 42 int f( [] x ); 33 34 [int] f(); 35 [double] x = 1.5; 36 37 - Multiary: [TYPE, TYPE, ...] 38 39 [bool, char] f(); 40 [short, int, long] x; 41 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. 43 46 44 47 Current syntax for tuple expressions: … … 46 49 - Nullary: (Same as `void`, use return without an expression.) 47 50 48 [] f( ) { return; } 49 [] f( ) { return [] ; } 50 51 - Unary: 52 53 [int] f( ) { return 3; } 54 [int] f( ) { return [3]; } 55 56 - Nary: [EXPR, EXPR] 57 58 [int,int] f( ) { return [3,4]; } 59 60 Currently, there is a parsing problem for nullary and unary tuple expression, 61 which is being looked at. Hence, only these two forms work. 62 63 [] f( ) { return; } // nullary 64 [int] f( ) { return 3; } // unary 65 51 [] f() { return; } 52 53 - Unary: EXPR (in return only) or {EXPR} 54 55 [int] f() { return 3; } 56 [int] x = {3}; 57 58 - Multiary: [EXPR, EXPR, ...] 59 60 [int, int] f() { return [3, 4]; } 61 [bool, char, int] x = [true, 'a', 3]; 62 63 Tuple expressions should be useable whenever an expression of that type is 64 expected. Issues with the current state will be discussed later. 65 66 66 Current syntax for tuple indexing is an integer constant, where its value 67 67 selects a tuple member, e.g.: 68 68 69 [char, int] tup; 70 tup = ['a', 0]; 71 char ch = t.0; // select first tuple member 72 int value = t.1; // select second tuple member 69 [char, int] tup = ['a', 0]; 70 char ch = tup.0; 71 int value = tup.1; 73 72 74 73 ### Mass and Multi-Assignment … … 79 78 80 79 [int, long, float] dst; 81 int src = 4 82 dst = src; // => dst.0 = src; dst.1 = src; dst.2 = src 80 int src = 4; 81 dst = src; 82 // Becomes: dst.0 = src; dst.1 = src; dst.2 = src; 83 83 84 84 Multi-assignment assigns every element in the destination tuple to the 85 85 corresponding element in the source tuple. Both tuples must be the same size 86 and the elements assignment compatible =>conversions.86 and the elements pairs must be assignment compatible conversions. 87 87 88 88 [long, int, float] dst; 89 89 [int, char, double] src = [1, 'a', 300.0]; 90 dst = src; // => dst.0 = src.0; dst.1 = src.1; dst.2 = src.1 90 dst = src; 91 // Becomes: dst.0 = src.0; dst.1 = src.1; dst.2 = src.1; 91 92 92 93 ### Tuple Restructuring … … 110 111 parmFunc(fst.0, fst.1, snd.0, snd.1); 111 112 112 There are few languages supporting multiple return-values as a standalone113 feature (SETL). Go has multiple return-values but restricts their use in114 matching arguments to parameters.115 116 func argFunc() (int, int) {117 return 3, 7118 }119 func parmFunc( a int, b int ) {120 fmt.Println(a, b )121 }122 func main() {123 parmFunc2( argFunc() ); // arguments must match exactly with parameters124 }125 126 113 ### Tuple Casts 127 114 … … 150 137 [int, [bool, char], int] w; 151 138 [i, b, c, i] = w; 152 [i, b, c, i] = ([int, bool, char, int])w; // fails 153 [i, b, c] = ([int, [bool, char]])w; // works 139 [i, b, c] = ([int, [bool, char]])w; 140 // But this does not work: 141 [i, b, c, i] = ([int, bool, char, int])w; 154 142 155 143 ### Polymorphic Tuple Parameters … … 160 148 sequence of types. 161 149 162 forall( T | { int ?>?( T, T ); } ) 163 T max( T v1, T v2 ) { return v1 > v2 ? v1 : v2; } // base case 164 165 forall(T, Ts... | { T max(T, T); T max(Ts); }) // recursive case 150 // Fixed Length Max - Base Case: 151 forall(T | { int ?>?( T, T ); }) 152 T max(T v1, T v2) { return v1 > v2 ? v1 : v2; } 153 154 // Variable Length Max - Recursive: 155 forall(T, Ts... | { T max(T, T); T max(Ts); }) 166 156 T max(T arg, Ts args) { 167 157 return max(arg, max(args)); 168 158 } 169 159 170 This feature introduces a type name into scope (Ts). It is used as a type but 171 because the tuple is flattened, the second assertion "T max(Ts);" matches types 172 with multiple parameters (the `...`), although it is used as a tuple function 173 inside the function body (max(args)). 174 175 The first non-recursive max function is the polymorphic base-case for the 176 recursion, i.e., find the maximum of two identically typed values with a 177 greater-than (>) operator. The second recursive max function takes two 178 parameters, a T and a Ts tuple, handling all argument lengths greater than two. 179 The recursive function computes the maximum for the first argument and the 180 maximum value of the rest of the tuple. The call of max with one argument is 181 the recursive call, where the tuple is converted into two arguments by taking 182 the first value (lisp car) from the tuple pack as the first argument 183 (flattening) and the remaining pack becomes the second argument (lisp cdr). 184 The recursion stops when the tuple is empty. For example, max( 2, 3, 4 ) 185 matches with the recursive function, which performs return max( 2, max( [3, 4] 186 ) ) and one more step yields return max( 2, max( 3, 4 ) ), so the tuple is 187 empty. 188 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. 181 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 } 189 189 190 190 Issues with the Current State … … 199 199 be. 200 200 201 Because of the fluid nature of tuples (flattening/structuring), routines like 202 constructions, destructor, or assignment do not make sense, e.g. this 203 constructor matches multiple a tuple types: 204 205 void ?{} ( [int, int, int] ? this ); 206 [int, [int, int]] x; // all match constructor type by flattening 207 [int, int, int] y; 208 [[int, int], int] z; 209 210 as could a similarly typed destructor or assignment operator. This prevents 211 tuples from being interwoven with regular polymorphic code. 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. 209 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; 214 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). 212 219 213 220 ### Providing TType Arguments is Inconsistent … … 217 224 functions and there are very few existing use-cases for ttype structures. 218 225 219 Passing arguments to a function inlines the arguments 220 while passing them to a polymorphic type requires them to be 221 enclosed in a tuple. Compare `function(x, y, z)` with `Type(A, [B, C])`. 222 223 This did not come up previously as there was little reason to explicitly 224 provide ttype arguments. They are implicit for functions and there is very 225 little use case for a ttype on a struct or union. 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. 242 243 ### Unary Tuple Value Syntax 244 245 Unary tuples don't use the standard tuple syntax and one of the two alternate 246 syntax options your may use instead doesn't work consistently. Solving both 247 of these issues would help with consistency. 226 248 227 249 ### Syntax Conflict 228 250 229 The tuple syntax conflicts with designators and the new C++-styleattribute251 The tuple syntax conflicts with designators and the C23 (C++-style) attribute 230 252 syntax. 231 253 232 struct S { int a[10]; } = { [2] = 3 }; // [2] looks like a tuple 233 [[ unused ]] [[3, 4]]; // look ahead problem 254 int a[10] = { [2] = 3 }; 255 256 Here [2] = 3 is an array designator, but has the same syntax as an assignment 257 to a tuple of references, it isn't until the resolver determains that 2 is 258 not a reference that that case can be ruled out. 259 260 [[ unused ]] [[int, int], int] x; 261 262 Here the opening `[[` could be a nested tuple or the beginning of an 263 attribute, the parser can't figure out until later. 234 264 235 265 These conflicts break C compatibility goals of Cforall. Designators had to 236 266 their syntax change and Cforall cannot parse the new attributes. 237 238 Although most of this redesign is about the semantics of tuples, but an update 239 to tuple syntax that removes these conflicts would improve the compatibility of 240 Cforall going forward (and also open up the new attribute syntax for cforall 241 features). 267 The behaviour of these cases, if they could be parsed, should be unchanged. 242 268 243 269 Change in Model 244 270 --------------- 245 271 This proposal modifies the existing tuples to better handle its main use 246 cases. There are some use cases that are no longer handled, and a new 247 struct tuple is added to cover those cases. 248 249 The new tuples is even more "unstructured" than before. New tuples are 272 cases. There are some use cases that are no longer handled, so in addition 273 to changing the existing "restructured tuples" to "unstructured tuples" 274 (or packs) a new "structured tuple" is added in the form of struct tuple. 275 276 The new tuples is even more "unstructured" than before. New tuples are 250 277 considered a sequence of types or typed entities. These packs are then unpacked 251 278 into the surrounding context. Put differently, tuples are now flattened as much … … 259 286 unstructured tuples no longer support. 260 287 261 Note that the underlying implementation might not actually look like this. 288 Note that the underlying implementation might not actually look like this, 289 but this is how it should seem to the user. 262 290 263 291 Changed Features … … 265 293 Some of the concrete changes to the features of the language. 266 294 295 ### Unstructured Tuple / Pack Type 296 297 The evolution of our current tuples (called packs in an attempt to make the 298 names more distinct). They work like the current tuples except they are 299 always flattened. You might still be able to declare nested tuples, but these 300 are not meaningful, they are flattened to a single level and, where 301 approprate, is inlined into the sounding content. 302 303 [bool, [char, int], long] x; 304 // Becomes: 305 [bool, char, int, long] x; 306 307 void f(bool a0, [char, [int, long]] a1, float a2); 308 // Becomes: 309 void f(bool a0, char a1_0, int a1_1, long a1_2, float a2); 310 311 [,] f(int a0, [,] a1, [bool,] a2); 312 // Becomes: 313 void f(int a0, bool a2); 314 315 This actually means that tuples do not participate in overload resolution 316 in the way the do currently. Restructuring is always free because they are 317 always reduced to the same form as part of type matching. 318 319 Packs are still not object types and do not have lifetime functions. They 320 cannot be used as object types, nor as any data type, but they can be still 321 used as ttypes. 322 323 (Unless they need to have lifetime functions for other features.) 324 267 325 ### Structured Tuple Type 268 326 269 There is a standard library or built-in type named `tuple`, it does not need a270 special syntax to write types or instances. The type definition might need some 271 primitive support, but if supported as a regular type would look something like 272 this:327 There is a standard library or built-in type named `tuple`, it does not need 328 a special syntax to write types or values. The type definition might need 329 some primitive support, but if supported as a regular type would look 330 something like this: 273 331 274 332 forall(Ts...) 275 333 struct tuple { 276 inline Ts all; // inline all specified fields334 inline Ts all; 277 335 }; 278 336 279 This type is constructed the same way as most types, a list initializer with 280 each tuple argument, and the lifetime functions (construction, assignment and 281 destruction) work the same. Field access works two ways, the first is accessing 282 the `all` field, effectively converting the structured tuple into an 283 unstructured tuple, the other is to use tuple indexing directly on the 284 structure as if it is an unstructured tuple. 285 286 (If `inline` does not work, just rename all to `get`. It does make things a bit 287 longer but has no change in functionality. If the `all` access does not work, 288 that is more problematic, and tuple slicing might provide a work around.) 337 This type wraps up an unstructured tuple, in this case a pack of members, 338 and gives it "edges" and so structure. It also gives tuples an interface 339 more like a normal structure type, for the anonymous structure use case. 340 341 The struct tuple does have lifetime functions, can use list initializer 342 syntax and otherwise be treated as a normal structure. You can use regular 343 member access syntax to convert the struct tuple into a unstructured pack, 344 writing `object.all`. 345 346 The `inline` modifier is a new specialized feature to allow you use tuple 347 index expressions directly on the type. With or without inline you should 348 be able to chain a tuple index expression onto the member expression, such 349 as `object.all.1`. The inline specifier allows you to skip the middle 350 and use `object.1`. 351 352 More complex operations can usually be preformed by taking the pack out of 353 the struct tuple and acting on it. Polymorphic operations only have to go one 354 level down to get to base operations, there is no need for recursive 355 construction, nor manually coding different sizes of tuple. This should give 356 most of the ease of working with a primitive tuple, because effectively you 357 are except it has been given fixed edges. 358 359 A tuple struct should also be an object type, and let's you pass multiple 360 values through a single polymorphic slot. 361 362 A note on the naming here, because there are more pieces that need names 363 instead of symbols. The name `tuple` follows because that is the common name, 364 although this means this is now the default tuple in some sense. The name of 365 the pack was picked to work with inline and make the difference between `.0` 366 and `.all` clear. (If inline doesn't work then `get` might be a better name.) 289 367 290 368 ### Type Qualifier Distribution … … 330 408 up, that cannot be enforced with two different tuples. 331 409 410 This example doesn't have to be implemented this way because we do have the 411 special case operations for these already. 412 332 413 ### Type Packs 333 414 … … 371 452 372 453 For local declarations it works similarly, except the name introduced is 373 directly usable as a tuple .454 directly usable as a tuple instead of going through a member access. 374 455 375 456 forall(Objects...) … … 380 461 } 381 462 463 ### Tuple Pack Restructuring 464 465 Implicit restructuring still occurs, but is not considered as part of 466 overload resolution. The structure of tuples is ignored, where individual 467 tuples begin or end is not considered, just the sequence of types. 468 469 This can be viewed as flattening both sides (the arguments/caller and the 470 parameters/callee) before type resolution. Both any nested packs and packs 471 into any larger context: 472 473 call(false, ['a', [-7,]], 3.14) 474 // Inline Nesting: 475 call(false, ['a', -7], 3.14) 476 // Inline into Context: 477 call(false, 'a', -7, 3.14) 478 479 The main context where tuples are inlined are parameter lists, in that you 480 can consider a parameter list as a single pack. Return types could be viewed 481 in this way, but because they are presented in a single type, they are 482 already wrapped in a tuple. 483 484 [int, [bool, char]] f(); 485 // Inline Nesting: 486 [int, bool, char] f(); 487 488 This also happens "after" any polymorphic parameters are instantiated. 489 490 Empty packs are also collapsed, logically being removed or replaced with 491 void as in the following example: 492 493 // For example, this function: 494 forall(Ts...) Ts example(int first, Ts middle, int last); 495 // With Ts = [,], should not be treated as: 496 [,] example(int first, [,] middle, int last); 497 // But instead: 498 void example(int first, int last); 499 500 Or closer to it, there are some slight differences between the nullary tuple 501 and void, for instance creating some instances for consistency with the other 502 tuple types. 503 504 The struct tuple is never implicitly restructured. 505 382 506 ### Tuple Declaration/Deconstruction 383 507 … … 386 510 elements of the tuple can be named. 387 511 388 [int quo, int rem] ret= divmod(a, b);512 [int quo, int rem] quorem = divmod(a, b); 389 513 390 514 Here `ret` refers to the tuple, the entire pack, while `quo` and `rem` … … 396 520 no named to access it. 397 521 398 PAB: I do understand the point of this. 522 This does not add any new functionality, more it is a qualify of life feature 523 making it very easy to give elements of a tuple descriptive names 524 instead of trying to combine a name for the whole tuple with an index. 525 (`quo` and `rem` vs. `quorem.0` and `quorem.1`) 399 526 400 527 ### Tuple Casts … … 405 532 so it may be replaced with other features (for example: tuple slicing). 406 533 407 ### Forbidden Tuples 408 409 The unstructured tuple cannot represent all the types that the previous 410 semi-structured tuple could. These cases still exist in various ways, 411 specifically in the internals of a polymorphic type, but in general should be 412 considered in their reduced form. 413 414 Forbidding some of these tuples may remove ambiguity and solve some syntax 415 conflicts that currently exist. 416 417 Nullary, or 0 element tuples, are equivalent to void, the type that carries 418 no data, because they do not either. It should either be replaced with void 419 or removed entirely when it appears in a larger sequence. 420 421 // For example, this function: 422 forall(Ts...) Ts example(int first, ????) 423 // With Ts = [], should not be treated as: 424 [] example(int first, [] middle, int last); 425 // But instead: 426 void example(int first, int last); 427 428 Unary, or 1 element tuples, should be the same as their element type. That 429 is to say a single type in an unstructured tuple is a no-op. 430 431 Lastly, nested tuples are always flattened to a one-depth tuple. This means 432 that `[bool, [char, int], float]` is resolved as `[bool, char, int, float]`, 433 with the internal structure of the tuple ignored. 434 435 The flatten into a large sequence rule mentioned above is actually just an 436 application of this. Unstructured tuples can already be restructured, even at 437 the top level of an function call. This can be expressed by considering the 438 argument list as a tuple: 439 440 call(false, ['a', -7], 3.14) 441 call([false, ['a', -7], 3.14]) 442 call([false, 'a', -7, 3.14]) 443 call(false, 'a', -7, 3.14) 444 445 The ability to write nested tuples may remain so tuple deconstruction can be 446 used to name a slice of the tuple. 534 // Allowed: 535 ([bool, short])tuple_bool_char; 536 ([int, float])tuple_float_float; 537 538 // Forbidden: 539 ([int, int, int])tuple_int_int; 540 ([long, [float, int]])tuple_long_float_int; 541 542 ### New Tuple Literal Syntax 543 544 In addition to the main redesign of this proposal, the syntax is updated to 545 be more consistant. It is now a "[]" list with "," separators and a ","/comma 546 terminator that is optional for tuples with 2 or more elements (those that 547 already have a comma in them) and manditory in other tuples. 548 549 It should also be symmetric between types and value, differing only in that 550 an element (ITEM below) of a tuple type is a type and the element of a tuple 551 value is a value. 552 553 - Nullary Tuple: [,] 554 - Unary Tuples: [ITEM,] 555 - Multiary Tuples: [ITEM, ITEM] [ITEM, ITEM,] [ITEM, ITEM, ITEM] ... 556 557 Currently, although syntax is provided to write them, but manually writing 558 a nullary or unary tuple should be very rare. This is because a nullary tuple 559 can either be erased or treated as void, while unary tuples can be treated 560 as their element type. Similary for nested tuples, can be treated as the 561 flattened tuple. 562 563 (Update: Orignal nullary tuple proposal was `[]`, but that had some issues.) 564 565 ### TType Argument Syntax 566 567 It would be nice to seamlessly provide packs of types in polymorphic 568 argument lists, however doing so requires a new restriction. 569 570 As an example of the change, consider providing arguments to a polymorphic 571 structure with a otype and a ttype parameter. 572 573 forall(T, Us...) struct D {...}; 574 D(int, [,]) // D(int) 575 D(bool, [char,]) // D(bool, char) 576 D(long, [int, short]) // D(long, int, short) 577 578 This is a nice syntaxtic improvement for the common case, it does restrict 579 some of the more complex cases, such as cases with two ttypes. There are 580 various restrictions on this for functions. If the rules make it unambigous 581 then the grouping could be omitted, or a more advanced version, it can be 582 ommited only in the indivual cases where it is unambigous. 447 583 448 584 ### Tuple Slicing (Range Tuple Indexing) … … 454 590 index expression to be a list-range a multiple sub-tuples can be extracted. 455 591 456 [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9].0~2,5~9~2457 458 produces the tuple 459 460 [ 0, 1, 2, 5, 7, 9]461 462 (Note the position and values are the same to simplify the example.) That is, 463 index the first 3 tuple elements, and then indexes elements 5 to 9 in steps of 464 2. Not all selections are possible with this mechanism (e.g., index the 465 Fibonacci elements), but it covers many cases.592 ['a', 'b', 'c', 'd', 'e', 'f'].0~3 593 ['a', 'b', 'c', 'd', 'e', 'f'].1~5~2 594 // Produces: 595 ['a', 'b', 'c'] 596 ['b', 'd'] 597 598 In words, the first indexes the first 3 tuple elements (0, 1, 2, stopping 599 before index 3) and the second indexes elements starting at index 1, stoping 600 before index 5 going in steps of 2. This does not allow for arbitary 601 selections, but it covers many common slices. 466 602 467 603 To get the new tuple, the range has to be constructed and traversed at compile … … 486 622 The iterators proposal has a section on some new range types, this feature 487 623 is intended to be built on that feature. Not simply reuse some of the syntax 488 that is also used in the special for loop. And compile-time evaluation may 489 need to be improved. 490 491 PAB, I don't understand this last part as the index range is compile time not 492 runtime. 624 that is also used in the special for loop. This may add some requirements 625 about the compile-time evaluation on range expressions to make sure these 626 expressions can be evaluated at compile-time. 493 627 494 628 Implementation … … 496 630 An overview of the implementation details of the new proposal. 497 631 498 ### Structured Tuple Implementation 499 500 Under the hood, unstructured tuples are implemented as structured tuples, with 501 the restructuring code inserted wherever needed. In short, the base 502 implementation should stay mostly the same. 503 504 PAB: The current implementation does not use convert unstructured tuples to 505 structured tuples. Look at the code generated for 506 507 int x, y; 508 [x, y] = 3; 509 [x, y] = [y, x]; 510 511 512 Actually inlining tuples can be done in some cases, it may even help with 513 some forms like a fixed tuple decomposition. However, polymorphic tuples 514 still need to be reduced to a single generic form, which would be a 515 polymorphic container, a tuple. 632 ### Structured Tuple 633 634 There is actually a decision point here. If it can be implemented entirely 635 in the standard library, that would be good. If it cannot, it can actually 636 be implemented as the same primitive implementation as we use now and may be 637 how unstructured tuple packs are implemented. 638 639 ### Member Pack Implementation 640 641 Implementing member packs can use the same pattern as current built-in tuple 642 syntax. This is a two stage process, first is by ... then the it can be 643 handled by the existing code for polymorphic but not variadic structures. 644 645 forall(Ts...) 646 struct tuple { 647 Ts get; 648 }; 649 650 So when used with diffences length an hidden declaration is created which 651 has the pack parameter replaced with the approprate number of object type 652 parameters. Such as in the following examples: 653 654 struct tuple$0 { 655 }; 656 657 forall(Ts$0) 658 struct tuple$1 { 659 Ts$0 get$0; 660 }; 661 662 forall(Ts$0, Ts$1) 663 struct tuple$2 { 664 Ts$0 get$0; 665 Ts$1 get$1; 666 }; 667 668 Then it can be handled by the regular boxing code. Detection of these cases 669 will have to changed because it is no longer a single case. But the pattern 670 does work if the structure has additional fields in it, as they are just 671 placed before and after the pack. 672 673 forall(U, Ts...) 674 struct blob { 675 int header; 676 Ts data; 677 U footer; 678 }; 679 680 forall(U, Ts$0, Ts$1) 681 struct blob$2 { 682 int header; 683 Ts$0 data$0; 684 Ts$1 data$1; 685 U footer; 686 }; 687 688 The `inline` member pack just effects the resolver. It should be expanded out 689 to the member access and then the tuple index expression. 690 691 ### Local Declaration Pack 692 693 Packs of local declarations can be handled very differently if they are 694 concrete or not. Concrete packs can be writen out in full (even if some 695 individual elements are polymorphic) can be inlined directly, expanding out 696 into a series of object declarations. 697 698 [A, B] local = func(); 699 call(local); 700 701 A local$0; 702 B local$1; 703 ?{}([&local$0, &local$1], func()); 704 call([local$0, local$1]); 705 706 In the polymorphic case though, that entire group needs to be reduced to a 707 block that can put into a generic function. Here we can use something like 708 the currently existing implementation, where the entire tuple an opaque 709 bundle, passed to adapters and thunks for handling. 710 711 This is also how tuple declarations are implemented. In the concrete case the 712 element names are real and the pack name is replaced with a tuple expression 713 of the elements. In the polymorphic case the pack name is real and the 714 element names are replaced with tuple index expressions. 715 716 ### Unstructured Tuple Implementation 717 718 Unstructured tuples have two general implementation stratagies. Either they 719 are erased and the packs inlined into their context, or they are wrapped 720 up as like a structured tuple, generally using the current implementation. 721 722 For example, if a concrete pack (size and all the types are known), appears 723 in a parameter list the pack can be split and inlined into the argument list. 724 725 void function(int a, [bool, char, short] b, float c); 726 // Becomes: 727 void function(int a, bool b0, char b1, short b2, float c); 728 729 On the other hand, if a polymorphic pack appears in the same place, it will 730 have to be wrapped up in a dynamic structure so it different instances can 731 732 forall(Ts...) 733 void function(int a, Ts b, float c); 734 // Becomes (after the implicit packing and unpacking operations): 735 forall(T) 736 void function(int a, T* b, float c); 516 737 517 738 ### AST Updates 518 739 519 The current AST cannot represent all the new features. Particularly, an 520 object declaration cannot name elements of the tuple. To this end a new 521 node type, `TupleDecl`, should be added to handle tuple deconstruction 522 declarations (other cases can still be handled with `ObjectDecl`). 740 The AST may already be capable of representing most of this. I have only 741 identified one feature that definitely will now work in the current AST, and 742 that are the object like tuple declarations with named elements. 743 744 Particularly, an object declaration cannot name elements of the tuple. 745 To this end a new node type, `TupleDecl`, 746 should be added to handle tuple deconstruction declarations 747 (other cases can still be handled with `ObjectDecl`). 523 748 524 749 This would act much like a `FunctionDecl` except for tuples, narrowing the … … 527 752 elements of the tuples. 528 753 529 PAB: the following parses:530 531 [int x, int y] foo( int p );532 533 and discussed by Till.534 535 754 (I'm not actually going to decide the implementation now, but some early 536 755 examination of the problem suggests that it might be better off wrapping a 537 756 series of `ObjectDecl` rather than just some strings to hold the names.) 538 757 539 ### Field Packs540 541 Field packs in structures probably have to be written out in full by the542 specialization pass. If not, it could have some negative effects on layout,543 causing a structure to take up extra space. It may be able to reuse some of the544 specialization code for the existing tuples.545 546 758 Related Features in Other Languages 547 759 ----------------------------------- 548 Other languages have similar features. Organized by the related feature. 760 Other languages have similar features. Organized by the related feature, 761 then language (this does mean we can and do hit languages multiple times). 549 762 550 763 (In hindsight, I may have gone overboard with the number of examples.) … … 584 797 - https://doc.rust-lang.org/reference/expressions/tuple-expr.html 585 798 586 #### C++ tuple799 #### C++ 587 800 588 801 Implemented as a template type defined in the standard library. No special … … 598 811 used, e.g., `std::get<0>( tuple )`. 599 812 813 C++ is an interesting case because it is one of the few cases where no part 814 of the tuple system is built-in. It is entirely created in the standard 815 library using templates. This also has massive error messages when something 816 goes wrong, hopefully our version of struct tuple will reduce the problem. 817 600 818 C++ also has structured binding, a kind of limited pattern matching. In a 601 structured binding declaration, you can write an auto typed declaration with 602 a list of identifiers in a `[]` list. 603 For example, `auto [first, second] = getSize2Tuple();`. 819 structured binding declaration, you can write an auto typed declaration where 820 a list of identifiers in a `[]` replaces the single identifier. 821 822 auto [first, second] = getSize2Tuple(); 823 824 The base type has to be `auto` because the base type for each element varies. 825 Still, qualifiers (cv and reference) on the auto will be distributed to types 826 on the individual members. 827 828 The type bound to the binding may be an array, a "plain" structure or any 829 type that implements the tuple-like interface (`std::tuple_size` and 830 `std::tuple_element`). 604 831 605 832 - https://en.cppreference.com/w/cpp/utility/tuple 606 833 - https://en.cppreference.com/w/cpp/language/structured_binding 607 834 608 PAB: I do not understand the syntax `auto [first, second]`. Where does it come 609 from? 610 611 #### C++ template 612 613 C++ templates can take various types of parameters, including parameter 614 packs. These contain series of values. These are used in pack expansion, 835 #### Haskell 836 837 Haskell has a special syntax for tuples, but otherwise they are completely 838 normal polymorphic types. Because of this, tuples have a maximum size. 839 Haskell (98) supports tuples of up to 15 elements and the standard library 840 has functions for tuples of up to 7 elements. 841 842 The syntax for tuples is a comma separated list of elements. Either element 843 types to create a tuple type, or element values to create a tuple value. The 844 context decides among them, such as `(6, "six")` or `(Bool, Char, Int)`. 845 846 Also all the elements can be removed, getting an expression like "(,)" or 847 "(,,,)", which can be then be used as a function, for a type, or an expression. 848 849 Haskell supports pattern matching as the main way to extract values from a 850 tuple, although helper functions "fst" and "snd" are provided for field 851 access on two element tuples. 852 853 Haskell does not have 0 or 1-element tuples. The nil type, written "()" for 854 both type and value, is effectively the 0 element tuple. There is also a type 855 called "Solo" that is a polymorphic structure with one field and is used as 856 the 1-element tuple, but has regular Haskell data-type syntax. 857 858 - https://www.haskell.org/onlinereport/basic.html 859 860 #### OCaml 861 862 OCaml only supports multi-element (2 or more) tuples. It does have the `unit` 863 type, which has one value, written `()`. Tuple types are written as a '*' 864 separated list of types, tuple values are written as ',' separated list of 865 expressions. Pattern matching tuples is supported and uses the same syntax as 866 values. Parenthesizing these lists is only needed for grouping. 867 868 - https://ocaml.org/docs/basic-data-types#tuples 869 870 #### Swift 871 872 Swift has tuple types that use the basic parenthesized, comma separated list of 873 types or values. It only supports 0 and 2 or more element tuples (the `Void` 874 type is an alias for the empty tuple type). 875 876 Swift also supports named tuples. Names can be added before the tuple element, 877 both for the tuple type and value. The syntax is a name followed by a colon, 878 e.g., `(first: int, second: int)`. These names are a fixed part of the type, 879 and can be used as part of field access notation (otherwise numbers are used 880 in-place of field names `tuple.0` vs. `tuple.first`). 881 882 - https://docs.swift.org/swift-book/documentation/the-swift-programming-language/types/#Tuple-Type 883 884 #### Python 885 886 In Python tuples are immutable lists. Because they are dynamically typed, 887 there is only one tuple type `tuple`. 888 889 It also has various named tuples. The first, namedtuple, allows naming the 890 elements of the tuple. The second, NamedTuple, is actually a way of creating a 891 typed record in a normally untyped language. 892 893 - https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range 894 - https://docs.python.org/3/library/collections.html#collections.namedtuple 895 - https://docs.python.org/3/library/typing.html#typing.NamedTuple 896 897 #### LISP 898 As LISP is dynamically typed, its `cons` data type is an untyped pair and is 899 (perhaps infamously) the main constructor of all compound data types. The 900 function `cons` takes two arguments and builds a pair. Functions `car` and 901 `cdr` get the first and second elements of the pair. 902 903 ### Packs 904 Packs (or unstructured tuples) are a much less common feature. In fact, there 905 might just be one language, C++, that supports packs. The most common use 906 case for unstructured tuples is returning multiple values, so there is a 907 comparison to languages that have that as a special feature. 908 909 #### Go 910 Go does not have built in tuple types, but it has multi-return syntax that 911 looks like the tuple syntax of many other languages. 912 913 ``` 914 func main() { 915 i, j := returnIntInt() 916 ... 917 } 918 919 func returnIntInt() (int, int) { 920 return 12, 34 921 } 922 ``` 923 924 Go can unpack multiple return values and pass them all to a function, but 925 the unpacked tuple must match the parameter list exactly. 926 927 ``` 928 func acceptIntInt(a int, b int) { 929 fmt.Println(a, b) 930 } 931 ``` 932 933 - https://golangdocs.com/functions-in-golang 934 - https://go.dev/src/go/types/tuple.go 935 936 #### Lua 937 Lua is a scripting language that is dynamically typed and stack based. Although 938 the stack is usually only directly visible in the C-API, it does allow any 939 function to return any number of values, one, zero or more, from any return 940 expression. 941 942 ``` 943 local funcion f() 944 return 12, 34 945 end 946 947 local i, j = f() 948 ``` 949 950 The last argument in an argument list can be an expression - function call - 951 with multiple results and all the results are passed to the function after 952 other arguments. 953 954 Because Lua is dynamically typed, multiple return values are allowed anywhere 955 and the length of the value listed is adjusted, discarding any extra values 956 and filling in new values with the value `nil`. This is also used if a 957 function is called with the incorrect number of arguments. 958 959 - https://lua.org/ 960 - https://lua.org/manual/5.4/manual.html#3.4.12 961 962 #### C++ 963 964 We return to C++ to view template parameter packs. These are the symmetric 965 with our pack feature (and both are even used to construct the structured 966 tuples in the last section). 967 968 C++ template parameter packs are a modification that can be applied to any 969 template parameter, so the parameter now takes a series of arguments instead 970 of just one. These are used in pack expansion, 615 971 which usually expand to a comma separated list, but it can also be a chain of 616 972 boolean binary operator applications. For example, if the parameter … … 643 999 644 1000 There are also fold expressions that use binary operators to combine a pack 645 into a single expression. For example, `args + ... + 0` which adds every 646 element of the `args` pack together. 1001 into a single expression. For example, you can write a variadic sum function 1002 that compiles down to the primitive additions. 1003 1004 ``` 1005 template<typename... Args> 1006 int sum(Args&&... args) { 1007 return (args + ... + 0); 1008 } 1009 ``` 647 1010 648 1011 C++ is about the best you could ask for in this area, but it does a lot of work … … 652 1015 - https://en.cppreference.com/w/cpp/language/parameter_pack 653 1016 - https://en.cppreference.com/w/cpp/language/fold 654 655 #### Haskell656 657 Haskell has a special syntax for tuples, but otherwise they are completely658 normal polymorphic types. Because of this, tuples have a maximum size.659 Haskell (98) supports tuples of up to 15 elements and the standard library660 has functions for tuples of up to 7 elements.661 662 The syntax for tuples is a comma separated list of elements. Either element663 types to create a tuple type, or element values to create a tuple value. The664 context decides among them, such as `(6, "six")` or `(Bool, Char, Int)`.665 666 Also all the elements can be removed, getting an expression like "(,)" or667 "(,,,)", which can be then be used as a function, for a type, or an expression.668 669 Haskell supports pattern matching as the main way to extract values from a670 tuple, although helper functions "fst" and "snd" are provided for field671 access on two element tuples.672 673 Haskell does not have 0 or 1-element tuples. The nil type, written "()" for674 both type and value, is effectively the 0 element tuple. There is also a type675 called "Solo" that is a polymorphic structure with one field and is used as676 the 1-element tuple, but has regular Haskell data-type syntax.677 678 - https://www.haskell.org/onlinereport/basic.html679 680 #### OCaml681 682 OCaml only supports multi-element (2 or more) tuples. It does have the `unit`683 type, which has one value, written `()`. Tuple types are written as a '*'684 separated list of types, tuple values are written as ',' separated list of685 expressions. Pattern matching tuples is supported and uses the same syntax as686 values. Parenthesizing these lists is only needed for grouping.687 688 - https://ocaml.org/docs/basic-data-types#tuples689 690 #### Swift691 692 Swift has tuple types that use the basic parenthesized, comma separated list of693 types or values. It only supports 0 and 2 or more element tuples (the `Void`694 type is an alias for the empty tuple type).695 696 Swift also supports named tuples. Names can be added before the tuple element,697 both for the tuple type and value. The syntax is a name followed by a colon,698 e.g., `(first: int, second: int)`. These names are a fixed part of the type,699 and can be used as part of field access notation (otherwise numbers are used700 in-place of field names `tuple.0` vs. `tuple.first`).701 702 - https://docs.swift.org/swift-book/documentation/the-swift-programming-language/types/#Tuple-Type703 704 #### Python705 706 In Python tuples are immutable lists. Because they are dynamically typed,707 there is only one tuple type `tuple`.708 709 It also has various named tuples. The first, namedtuple, allows naming the710 elements of the tuple. The second, NamedTuple, is actually a way of creating a711 typed record in a normally untyped language.712 713 - https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range714 - https://docs.python.org/3/library/collections.html#collections.namedtuple715 - https://docs.python.org/3/library/typing.html#typing.NamedTuple716 717 #### LISP718 As LISP is dynamically typed, its `cons` data type is an untyped pair and is719 (perhaps infamously) the main constructor of all compound data types. The720 function `cons` takes two arguments and builds a pair. Functions `car` and721 `cdr` get the first and second elements of the pair.722 723 ### Packs724 Packs (or unstructured tuples) are a much less common feature. In fact, there725 might just be one language, C++, that supports packs. The most common use726 case for unstructured tuples is returning multiple values, so there is a727 comparison to languages that have that as a special feature.728 729 #### Go730 Go does not have built in tuple types, but it has multi-return syntax that731 looks like the tuple syntax of many other languages.732 733 ```734 func main() {735 i, j := returnIntInt()736 ...737 }738 739 func returnIntInt() (int, int) {740 return 12, 34741 }742 ```743 744 - https://golangdocs.com/functions-in-golang745 - https://go.dev/src/go/types/tuple.go746 747 #### Lua748 Lua is a scripting language that is dynamically typed and stack based. Although749 the stack is usually only directly visible in the C-API, it does allow any750 function to return any number of values, even a single return, in the return751 expression752 753 ```754 local funcion f()755 return 12, 34756 end757 758 local i, j = f()759 ```
Note: See TracChangeset
for help on using the changeset viewer.