language icon indicating copy to clipboard operation
language copied to clipboard

allow to destructure record types with positional fields

Open ekuleshov opened this issue 2 months ago • 9 comments

The Dart language allows to define a positional fields record and then destructure its fields like so:

  final (int, int, int) color = (1, 2, 3);  
  final (r, g, b) = color;
  print(r);

Also, in the following snippet the record type declaration containing field names is valid, but the field names declared in the record type can't be dereferenced:

  final (int r, int g, int b) color = (1, 2, 3);  
  print(r); // <-- "undefined name 'r'" at compile time

It would be great if the Dart language allowed to dereference these values by name. This would be a really handy feature when records with positional fields are passed around.

Consider the following example. The record type declaration in the map() method is valid, but these parameters can't be used in the lambda expression.

  List<String> list = ['a', 'b', 'c'];
  print(list.indexed.map(((int index, String s) en) => '$index:$s').join(', '));

Currently we have instead write lambda like (en) => '${en.$1}:${en.$2}', which is much less readable code.

ekuleshov avatar May 03 '24 15:05 ekuleshov

This are more fitting for the Dart Language issue tracker. Also, your last suggestion are very similar to: https://github.com/dart-lang/language/issues/3001

julemand101 avatar May 03 '24 15:05 julemand101

This are more fitting for the Dart Language issue tracker. Also, your last suggestion are very similar to: dart-lang/language#3001

Wasn't sure if that is a language feature or a compiler issue. As you can see compiler allows to specify field names, but it does not allow to dereference them.

Please move/triage as you see fit.

ekuleshov avatar May 03 '24 15:05 ekuleshov

  final (int r, int g, int b) color = (1, 2, 3);  

Since you have put the variable name color in here, it means you are creating a variable named color that contains a Record defined as the type (int r, int g, int b). The name of each variable in the record are not getting used so it kinda pointless besides for documentation.

Are you asking for this syntax instead should define the variable r, g, b and color? If so, it gets a bit confusing when having records with named arguments since should they also be defined as variables in your code or not?

final (int r, int g, int b) = (1, 2, 3);

Here, we don't have color so we can assume you want to destruct the record into three variables.

julemand101 avatar May 03 '24 16:05 julemand101

Here, we don't have color so we can assume you want to destruct the record into three variables.

I know. The catch is that compiler allows these param names when record name is declared after its type declaration. And that declaration looks similar to what you get when declaring record type in parameters of other methods.

In my other example, the type of en parameter ((int index, String s) en) => ... is accepted by the Dart compiler, but the compilation errors come from dereferencing index and s there.

ekuleshov avatar May 03 '24 16:05 ekuleshov

The syntaxes for types and for patterns are eerily similar, for very good reasons, which is actually why what you're trying doesn't work The parser needs to figure out whether it is a seeing a type or a pattern, and it uses the following identifier to decide that what came before is a type, not a pattern.

So you would have to write

final (int r, int g, int b) & color = ...;

to bind all the names ... if declaration patterns allowed & patterns. (They probably should, but today they don't.)

lrhn avatar May 03 '24 17:05 lrhn

We can actually use a variant, as long as we stick to an <outerPattern> as the outermost construct:

void main() {
  final ((int r, int g, int b) && color) = (2, 3, 4);
  ...
}

eernstg avatar May 03 '24 17:05 eernstg

The parser needs to figure out whether it is a seeing a type or a pattern, and it uses the following identifier to decide that what came before is a type, not a pattern.

I wonder if parser could delay its decision about exact type or infer a pattern for the time being until it is time to disambiguate exact type?

I used variable declaration for record, just to illustrate that compiler takes it as a valid syntax. Don't really have use for that variable (unless Dart would allow to do something like color.r instead of direct reference to r).

Other syntax variants discussed here and in dart-lang/language#3001 are adding some syntactic noise. Out of all, this one (:int r, :int g, :int b) would be concise in context of list.indexed.map(((:int index, :String s)) => '$index:$s').

ekuleshov avatar May 03 '24 18:05 ekuleshov

See also https://github.com/dart-lang/language/issues/3487.

mmcdon20 avatar May 03 '24 20:05 mmcdon20

So this is a request for two things:

  • Allow final pattern id to match the same ways as final (pattern && id). I believe we considered that during the design, and it was mainly parsing ambiguities that made us not do that. You have to do something to make final (int x, int y) point = ... treat (int x, int y) as a pattern instead of a type. Something like final ((int x, int y) & point) = ... (thanks @eernstg). I don't expect that to change. Something like https://github.com/dart-lang/language/issues/3487 does sound more likely (but still not something there are any current plans for).
  • Allow destructuring in parameter lists. That's a known request that we really want to deliver. The issue for that is https://github.com/dart-lang/language/issues/3001, so that only leaves the first item for this issue.

This is a language issue, so moving to language repository.

lrhn avatar May 07 '24 08:05 lrhn