Skip to content

Pattern matching an element in the middle of a list. #3416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
domesticmouse opened this issue Oct 19, 2023 · 5 comments
Closed

Pattern matching an element in the middle of a list. #3416

domesticmouse opened this issue Oct 19, 2023 · 5 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@domesticmouse
Copy link
Member

I can see how to match at the start or end of a list:

  final list = [1,2,3];
  
  if (list case [1, ...final postfix]) {
    print('postfix: $postfix');
  }
  if (list case [...final prefix, 3]) {
    print('prefix: $prefix');
  }

I'd like to be able to match an element in the middle of a list, akin to:

  final list = [1,2,3];
  
  if (list case [...final prefix, 2, ...final postfix]) {
    print('prefix: $prefix');
    print('postfix: $postfix');
  }

Unfortunately, at most one rest element is allowed in a list or map pattern.

I can understand why this might be an issue algorithmically, but arguably matching against the end of the list could be O(n) if the list is a linked list.

@domesticmouse domesticmouse added the feature Proposed language feature that solves one or more problems label Oct 19, 2023
@lrhn
Copy link
Member

lrhn commented Oct 19, 2023

A list pattern checks each element pattern against a list element at a fixed position. It checks each sub-pattern only once.
A case pattern is a combination of three things: destructuring, testing and binding. A declaration pattern is only destructuring and binding (except for list and map patterns, where destructuring includes testing length/containsKey, which is why they are the only declaration patterns which can fail).

The destructuring takes the original object, and extracts one value, which can then be tested, bound or further destructured by a sub-pattern.
The things you can extract by destructuring is something that has a unique, statically known, accessor - an interface getter, list index or map key.
Each sub-pattern is applied exactly (or at most) once.

What you're asking for is a search, like contains, which would check a sub-pattern against multiple elements, with no known accessor, until finding one which satisfies it.
It's more like a RegExp pattern than a destructuring pattern. (In fact, you could probably implement a rudimentary RegExps with such a search feature, applied to a string.split("") list, or at least a glob-pattern.)

It's not technically impossible to add such a search feature, but it is a fourth kind of operation, different from destructuring, and quite a lot more complicated.
I'd consider it unlikely that we'll add that.

(A linked list is not a List, the List interface guarantees efficient length and operator[] members.
If somebody creates a List subtype with linear time [], they're not implementing the List contact.)

@munificent
Copy link
Member

I agree with @lrhn. The functionality you describe is definitely useful, and I can see us adding some kind of active-pattern-like form where users could define this themselves (#2104). But I don't think it makes sense for the built-in pattern matching syntax to have this kind of subtlety. Consider:

final list = [1, 2, 3, 2, 4, 5];

if (list case [...final prefix, 2, ...final postfix]) {
  print('$prefix then $postfix');
}

What should that print? If the answer is "[1] then [3, 2, 4, 5]" then users will definitely request another way to get it to answer "[1, 2, 3] then [4, 5]" so we'd need some kind of "lazy rest param". And then we have to think about what happens if you mix and match those and... there's a lot of complexity there.

@munificent munificent closed this as not planned Won't fix, can't repro, duplicate, stale Oct 24, 2023
@escamoteur
Copy link

@munificent wouldn't it make sense at least to have a contains matcher for Strings or itterables?

@lrhn
Copy link
Member

lrhn commented May 14, 2024

IMO, It makes sense to allow any member to be used in an object pattern, not just getters.
It would still require only constant arguments, so that the result can be cached, but something like:

if (list case List(contains(2): true)) print("Has 2!");
if (list case List(indexOf(2): >= 0 && var i2)) print("${list.sublist(0, i2)}/${list.sublist(i2 + 1)})");

I would allow any non-assignment cascade selector as the object property selector, so also:

if (json case Map(["peoples"]: Map(["victors"]: List victors))) print("All Victor's: $victors");

@escamoteur
Copy link

escamoteur commented May 19, 2024

Especially for checks for Strings I was thinking about something like this:

switch (message) case ['error] {
case ['acess denied'] :  throw AuthenticationError();
case ['not found'] : throw NotFoundError()
}

the [] acts just as an example for a contain operator. As such checks are quite common it would be much easier to read than what we need today to achieve this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

4 participants