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