dart-lang / language

Design of the Dart language
Other
2.65k stars 201 forks source link

Why can't I write a guard clause for each pattern in the guard clause of pattern matching? #4094

Open yukisakai1225 opened 5 days ago

yukisakai1225 commented 5 days ago

In the following code, “B” is assigned to res. This is because the guard clause is evaluated after pattern matching in line 3.

final (a, b) = (0, 0);
final res = switch(a) {
  == 0 || _ when b == 1 => "A",
  _ => "B",
};

I expected the guard clause to affect each pattern as follows.

final (a, b) = (0, 0);
final res = switch(a) {
  == 0 || (_ when b == 1) => "A",
  _ => "B",
};

Consider other languages. In Swift, we can write guard clauses for each pattern, but in Rust and Scala, we cannot write guard clauses for each pattern. I would like to know the background behind this kind of specification for Dart.

I am not good at English, so sorry if my writing is not clear.

hydro63 commented 5 days ago

From what you are saying, you want to be able to use when also in subpatterns, right? Something like this?

var dummy = rnd.nextDouble();
x = switch(obj){
  (Foo() || (Bar(b: var b) when b > 10) || (Baz(c: var c) when c == 0)) when dummy > 0.5 => ...
  ...
}
// aka having multiple when clauses in patterns, each one for it's own subpattern

If that's what you want, i believe the reason is because it makes it more difficult to read what is actually matching instead of just having one when, where you can put all the additional checks. Another reason is that it makes patterns somewhat more difficult to compose, since one inner when clause can alter the execution of the entire pattern.

There is one issue that has been talking about this (#4057), but only very briefly. It also proposes allowing functions in pattern, which would effectively be able to do the more complex subpattern checking, that you (probably) want.

mateusfccp commented 5 days ago

I know this is what you are asking for, but sometimes you can rethink how you are going to express your code.

For instance, the above could be written as

final r = (0, 0);
final res = switch (r) {
  (0, _) || (_, 1) => "A",
  _ => "B",
};

Or

final (a, b) = (0, 0);
final res = switch(a) {
  0 => "A",
  _ when b == 1 => "A",
  _ => "B",
};

Either way are more readable IMO, so you may want to use them, as currently it's impossible to do what you want with the syntax you want.

yukisakai1225 commented 5 days ago

From what you are saying, you want to be able to use when also in subpatterns, right? Something like this?

You are right. Thank you for taking my intentions into consideration.

As you say, Separating the pattern and guard clauses would dramatically change the readability of the implementation regarding pattern matching.

yukisakai1225 commented 4 days ago

@mateusfccp I want to separate a line for each value returned after the pattern match. So, this sentence is a little less than my favorite.

final (a, b) = (0, 0);
final res = switch(a) {
  0 => "A",
  _ when b == 1 => "A",
  _ => "B",
};

I prefer the first example you presented.

final r = (0, 0);
final res = switch (r) {
  (0, _) || (_, 1) => "A",
  _ => "B",
};

If we want to add a guard clause to either (0, ) or (, 1), we need to separate the lines, as follow. I am looking for a way to avoid this.

final r = (0, 0);
final res = switch (r) {
  (0, _)  => "A",
  (_, 1) when xxxx => "A",
  _ => "B",
};
lrhn commented 3 days ago

There's is the option of not using a switch expression, but a switch statement, where each body can have more than one case:

final r = (0, 0);
final String res;
switch (r) {
  case (0, _):
  case (_, 1) when xxxx:
    res = "A";
  case _: 
    res = "B";
}

It has only one line for each value signed to res, and individual when clauses for each pattern.