dart-lang / language

Design of the Dart language
Other
2.66k stars 205 forks source link

Pattern matching operator #4128

Open TesteurManiak opened 2 weeks ago

TesteurManiak commented 2 weeks ago

Suggestion

In Swift, you can change the way pattern matching is handled on an object (or struct) by overloading a pattern matching operator ~=. It would be nice to have a similar feature for Dart objects.

Swift Example

struct Circle {
  var radius: Double
}

func ~= (pattern: Double, value: Circle) -> Bool {
  return value.radius == pattern
}

let myCircle = Circle(radius: 5)

switch myCircle {
  case 5:
    print("Circle with a radius of 5")
  case 10:
    print("Circle with a radius of 10")
  default:
    print("Circle with a different radius")
}

Dart Example

class Circle {
  const Circle({required this.radius});

  final double radius;

  @override
  bool operator ~=(double pattern) => radius == pattern;
}

final myCircle = Circle(radius: 5);

switch (myCircle) {
  case 5:
    print("Circle with a radius of 5");
  case 10:
    print("Circle with a radius of 10");
  default:
    print("Circle with a different radius");
}
hydro63 commented 2 weeks ago

Very similar to functions as patterns as proposed in #4057.

The referenced issue talks about allowing delagating pattern matching to a function, where the function would also be able to return T? instead of just bool. That allows it to be used in destructuring, not just testing, since you can just bind the returned value, and if it's null refute the pattern.

lrhn commented 2 weeks ago

This example doesn't really motivate this operator, and the linked documentation also doesn't give any examples of why it's necessary. It says it enables using in case switches, but you can use any object in case patterns in Dart using object patterns.

That is:

switch (myCircle) {
  case Circle(radius: == 5):
    print("Circle with a radius of 5");
  case Circle(radius: == 10):
    print("Circle with a radius of 10");
  default:
    print("Circle with a different radius");
}

It's longer, but more explicit. (If you implement operator == of Circleto be equal to doubles, then it works like what you ask for. You shouldn't because, it's not symmetric.)

A function that does equality does not work with nested patterns, not unless we have first class patterns. (It cannot do the equivalent of Circle(radius: < 5).)

The alternative in Dart would be an automatic coercion, where a pattern match against a Circle would automatically read the radius getter, and then let pattern matching work on that instead. Dart generally doesn't do automatic coercions.

It was discussed while developing patterns, that a class could have an implicit coercion to a record, so you could match against that in a pattern match. Object patterns make that unnecessary. You can give the class a normal getter returning that record and write TheClass(theGetter: pattern) and get the same effect without the implicit coercion.