www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Unpacking syntax

reply Timon Gehr <timon.gehr gmx.ch> writes:
These are my ideas for how to support unpacking any tuple-like type into 
multiple components.


Synopsis:

```d
import std.typecons: t=tuple, T=Tuple;

void main(){
     // unpack declarations
     auto (a, (b, c)) = t(1, t(2, "3"));
     assert(t(a, b, c) == t(1, 2, "3"));

     import std.stdio, std.string, std.conv;
     auto (u, v) = readln().strip.split.to!(T!(int,int));

     // can unpack in foreach
     foreach(i, (x, y); [t(1, 2), t(3, 4)]) {
         assert(x==2*i+1 && y==2*i+2);
     }

     // works with storage classes
     auto arr = [t(1, 2), t(3, 4)];
     foreach((ref x, y); arr) {
         x = 2*y;
     }
     foreach(const (x, y); arr) {
         static assert(is(typeof(x) == const(int)));
         static assert(is(typeof(y) == const(int)));
         assert(x == 2*y);
     }

     // works with opApply
     static struct Iota2d{
         int start,end;
         int opApply(scope int delegate(T!(int,int)) dg){
             foreach(i; start .. end) {
                 foreach(j; start ..end) {
                     if(auto r = dg(t(i,j)))
                         return r;
                 }
             }
             return 0;
         }
     }
     bool[4][4] visited;
     foreach((x, y); Iota2d(0,4)){
         visited[x][y] = true;
     }
     import std.algorithm;
     assert(visited[].all!((ref x)=>x[].all));

     // works with ranges
     import std.range;
     foreach(i, (j, k); enumerate(arr)) {
         writeln(i," ",j," ",k); // "0 4 2\n1 8 4\n"
     }

     // can unpack in lambda parameter list
     [t(1, 2), t(2, 3)].map!( ((a, b)) => a+b ).each!writeln; // "3\n5\n"

     // works with storage classes
     arr.each!( ((ref x, y)){ x = 3*y; });
     assert(arr.all!( (const (x, y)) => x == 3*y));
}
```

The code above works with my implementation, which can be found at:
https://github.com/tgehr/dmd/tree/unpacking


An unpacking declaration works if the right-hand side is an expression 
sequence or has `alias this` to an expression sequence. The number of 
elements has to match exactly.

Each variable without explicitly declared type that is unpacked to needs 
to have at least one storage class.

```d
auto (a, b) = t(1, 2); // ok
(auto a, auto b) = t(1, 2); // ok

(a, auto b) = t(1, 2); // error
(auto a, b) = t(1, 2); // error
```

This is less confusing and would allow this syntax to be used for mixed 
variable declaration and reassignment in the future.


Types can be declared explicitly, or inferred, independently for each 
variable:
```d
(int a, (string b, auto c)) = t(1, t("2", 3.0f));
```

Note that it is _not_ possible to declare a type for the whole unpacking 
explicitly:

```d
Tuple!(int, int) (a, b) = t(1, 2); // error
```

Storage classes can be applied to all unpacked variables independently.

```d
(auto a, const b, immutable c) = t(1, 2, 3);
```

Unpacking works with all variants of the `foreach` statement. (See 
synopsis for some examples, `foreach((x, y); a .. b)` can work too if 
`a` and `b` happen to be tuple-like types.) Here, storage classes are 
not required, consistent with how `foreach` works without unpacking.


Unpacking works in the parameter list of a function literal:

```d
int function(Tuple!(int, int)) f = ((x, y)) => x + y;

auto summands = t(1, 2);
writeln(f(summands))); // "3\n"
```

Unpacking does not work in the parameter list of a function that is not 
a literal. The reason for this is that there is no canonical type for 
the corresponding parameter:

```d
auto foo((int a, int b), int c){} // error
```

This restriction can be lifted at some point if we add built-in tuple types.


In `foreach` statements and in function literal parameters, the `ref` 
storage class can be applied to individual variables within an 
unpacking. They will cause the entire structure to be passed by `ref`, 
but non-`ref` unpacked variables will be initialized by value. (See 
synopsis for some examples.)

The `lazy` storage class is not supported for unpacked parameters, as 
that does not seem to make sense.



Limitations:

- The `auto ref` storage class is not currently supported on unpacking 
declarations.

- Applying the `out` storage class to individual variables within an 
unpacking is not currently supported.


I think this is a decent minimum viable product in terms of unpacking.



Future work:
- Move semantics for unpacking. (Currently it will do too many copies.)

- Do we want some way to define a manual unpacking without `alias this`?

- Do we want to be able to directly unpack static arrays and array 
slices of the correct length?

- `auto ref` support.

- `out` parameters in unpackings, e.g. `((in x, out y)){ y=x; }`

- Do we want a way to partially unpack? E.g., `auto (x, y, ...) = t(1, 
2, 3, 4);`
- Wildcards. E.g., `auto (_, x) = t;`

- General tuple syntax. (WIP at: 
https://github.com/tgehr/dmd/tree/tuple-syntax )
Sep 05
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Looks quite nice.

A nice consequence of the type/storage class being required, is that 
we'd be able to support struct unpacking for fields as well with my 
proposed member-of-operator.

``(:field name) = s;``

I can probably assist in a first iteration of the DIP document after 
I've done member-of-operator which is up next. If that would be helpful.

Regardless, thanks for doing the thread write up, it seems fairly sane 
and expandable!
Sep 05
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 9/6/24 00:08, Timon Gehr wrote:
 These are my ideas for how to support unpacking any tuple-like type into 
 multiple components.
 
 ...
Jacob asked on Discord whether in a `foreach` statement, key and value can both be unpacked, and whether in a foreach statement, `(const x, y)` is also allowed. Both of those work. In particular, this compiles and runs with my `unpacking` branch of DMD: ```d import std.typecons: t=tuple, T=Tuple; void main(){ auto aa=[t(1, 2): t(3, 4), t(5, 6): t(7, 8)]; foreach((const k0, k1), (v0, ref v1); aa){ k1 = k0; v1 = v0; v0 = 22; } assert(aa == [t(1, 2): t(3, 3), t(5, 6): t(7, 7)]); foreach((const x, y); [t(1, 2), t(2, 3)]){} } ```
Sep 06
prev sibling next sibling parent Paolo Invernizzi <paolo.invernizzi gmail.com> writes:
On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
 These are my ideas for how to support unpacking any tuple-like 
 type into multiple components.

 [...]
This is an excellent improvement to language practical usability. A big "thank you" to Timon, and a big +1 for going forward with it from my side. /Paolo
Sep 06
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
 These are my ideas for how to support unpacking any tuple-like 
 type into multiple components.
Looks good. Now that "ref" is allowed in variable declarations, any reason not to allow it in unpacking ones?
Sep 16
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 9/16/24 10:49, Max Samukha wrote:
 On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
 These are my ideas for how to support unpacking any tuple-like type 
 into multiple components.
Looks good. Now that "ref" is allowed in variable declarations, any reason not to allow it in unpacking ones?
I think it will just work or not work the same way as in non-unpacking accesses to tuple indices.
Sep 23