idea for Native D Tuple Syntax, packing and unpacking

Quirin Schroll qs.il.paperinik at gmail.com
Mon Jun 24 17:17:25 UTC 2024


On Sunday, 23 June 2024 at 04:10:35 UTC, Kindly Doright wrote:
> Idea for Native D Tuple Syntax, packing and unpacking (for 
> simplicty of compiler internals and identifiable language 
> construct)
> ```d
> // #() = #();
> #(a, b, c, d) = #(1, 'strval', [1,2,3], true);
>
> // myTuple = #(values); #(values) = myTuple;
> auto y = #(1, 'strval', [1,2,3], true);
> #(a, b, c, d) = y;
> ```
Makes sense so far.

> ```d
> // Optional unpack assignment
> #(a) =  #(1, 'strval', [1,2,3], true); // a = 1
> ```

Hard no to this one, but can be fixed easily:
```d
#(a, ...) = #(1, 'strval', [1,2,3], true); // a = 1
```

> ```d
> #(, b, , d) =  #(1, 'strval', [1,2,3], true); // b = 'strval, d 
> = true
> ```

Making the absence of something meaningful is generally not wise. 
I’d rather have `_` have special meaning in matching to mean 
discard.

Years ago, I implemented `tie[]` using `opIndex` and 
`opIndexAssign` which used `typeof(null)` as a discard and you 
can use `enum typeof(null) _` to just use `_` for discarding.  A 
cut-down proof-of-concept implementation is below.

What my implementation can’t do because it would require 
first-class language support is tuple-rest unpacking. It also 
can’t declare variables. Borrowing your syntax:
```d
#(_, b, _, d) = #(1, "strval", [1,2,3], true); // b = 'strval, d 
= true
```

> ```d
> // Embedded Tuple Syntax
> #(a, b, c, d, eee) = #(1, 'strval', [1,2,3], true, #(5, 
> 'str2'));
> #(f, g) = eee; // f = 5, g = 'str2'
> ```

What about:
```d
#(a, b, c, d, #(e, f)) = #(1, 'strval', [1,2,3], true, #(5, 
'str2'));
```
What about:
```d
#(a, b, cd..., e, f) = #(1, "strval", [1,2,3], true, 5, "str2");
#(c, d) = cd; // c == [1,2,3], d == true
```

If it were up to me, I’d use `[…]t` syntax for tuples and `[…]s` 
for static arrays.

Unpacking would be:
```d
[x, y]t = rhs; // for `x` and `y` already defined
auto [x, y]t = rhs; // Declares `x` and `y`.
[x, auto y]t = rhs; // for `x` already defined; declares `y`.

// sets `x`, `y`, and `zs` where `zs` must be a type such that 
`zs[0]`, …, `zs[n-1]` work, where `n` is `rhs.length - 2` if it 
is already declared:
[x, y, zs...]t = rhs; // for `x`, `y`, and `zs` already defined
auto [x, y, zs...]t = rhs; // / Declares `x`, `y`, and `zs`.
[auto x, y, auto zs...]t = rhs; // for `y` already defined; 
declares `x` and `zs`.
```
Static arrays would also work, but require/produce same-type 
values, possibly using common-type inference.

---

```d
struct tie
{
     import core.lifetime;
     import std.typecons : tuple;
     static opIndex(Ts...)(auto ref Ts args) => 
tuple(forward!args);

     alias opIndexAssign = opIndexOpAssign!"";
     template opIndexOpAssign(string op)
     {
         static void opIndexOpAssign(R, Ts...)(R rhs, auto ref Ts 
lhs)
         {
             static foreach (i; 0 .. Ts.length)
             {
                 static if (!is(Ts[i] : const(typeof(null))))
                 {
                     static assert(__traits(isRef, lhs[i]));
             		mixin("lhs[i] ", op,"= rhs[i];");
                 }
             }
         }
     }
}

void main()
{
     int x;
     double y;
     tie[x, y] = tie[2, 3.14];
     assert(x == 2);
     assert(y == 3.14);
     tie[x, y] += tie[3, 2.71];
     assert(x == 5);
     assert(y == 3.14 + 2.71);
     int z;
     enum _ = null;
     tie[z, _] = tie[x, y];
     assert(z == x);
}
```


More information about the dip.ideas mailing list