Skip to content
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

Can patterns match an enum field? #2844

Closed
rubenferreira97 opened this issue Feb 13, 2023 · 4 comments
Closed

Can patterns match an enum field? #2844

rubenferreira97 opened this issue Feb 13, 2023 · 4 comments
Labels
request Requests to resolve a particular developer problem

Comments

@rubenferreira97
Copy link

rubenferreira97 commented Feb 13, 2023

Trying the latest dart version I came across this example:

enum Difficulty {
  easy(1, "Easy"),
  medium(2, "Medium"),
  hard(3, "Hard");

  const Difficulty(this.id, this.designation);
  final int id;
  final String designation;
}

final json = jsonDecode('{"difficultyId": 1}');

// The expression of a constant pattern must be a valid constant.
if (json case {'difficultyId': Difficulty.easy.id}) {
   print("Got easy!);
}

Is there an alternative syntax that can solve this problem or this is not possible at all?

Edit:
Is this the correct solution?

if (json case {'difficultyId': int difficultyId} when difficultyId == Difficulty.easy.id) {
   print("Got easy!);
}

Still don't know how to express this statement (check case {'difficultyId': int difficultyId} and multiple when branches):

// Ugly, old syntax except case
if (json case {'difficultyId': int difficultyId}}) {
   if (difficultyId == Difficulty.easy.id) {
     print("Got easy!");
   } else if (difficultyId == Difficulty.medium.id) {
     print("Got medium!");
   } else if (difficultyId == Difficulty.hard.id) {
     print("Got hard!");
  } else {
    throw Exception('Id must be 1,2 or 3');
  }
}
@rubenferreira97 rubenferreira97 added the request Requests to resolve a particular developer problem label Feb 13, 2023
@rubenferreira97 rubenferreira97 changed the title Can patterns match a enum field? Can patterns match an enum field? Feb 13, 2023
@munificent
Copy link
Member

munificent commented Feb 16, 2023

Is this the correct solution?

if (json case {'difficultyId': int difficultyId} when difficultyId == Difficulty.easy.id) {
   print("Got easy!);
}

Yes. You can't match directly on Difficulty.easy.id because calling .id on an object isn't a valid constant expression. (We have discussed extending const to understand that getters on enums are (probably?) constant, but we haven't made any motion on that yet.)

Still don't know how to express this statement (check case {'difficultyId': int difficultyId} and multiple when branches):

It's a little hard with your particular example because you're getting strings from JSON and not converting them to enum values first, but the integer values for each enum value are also not stored in constants. Even without patterns, you'd have similar problems trying to write this code today using a switch statement.

With the new pattern stuff, you could do:

switch (json) {
  case {'difficultyId': int id} when id == Difficulty.easy.id:
     print("Got easy!");
  case {'difficultyId': int id} when id == Difficulty.medium.id:
     print("Got medium!");
  case {'difficultyId': int id} when id == Difficulty.hard.id:
     print("Got hard!");
  default:
    throw Exception('Id must be 1,2 or 3');
}

That's fairly verbose (though still better than what you'd write today). Really, the map patterns aren't accomplishing much since you're only accessing a single element and it's always the same one in each case. Maybe better as:

switch (json['difficultyId']) {
  case int id when id == Difficulty.easy.id:
     print("Got easy!");
  case int id when id == Difficulty.medium.id:
     print("Got medium!");
  case int id when id == Difficulty.hard.id:
     print("Got hard!");
  default:
    throw Exception('Id must be 1,2 or 3');
}

Or even (pretty weird):

var id = json['difficultyId'];
switch (null) {
  case _ when id == Difficulty.easy.id:
     print("Got easy!");
  case _ when id == Difficulty.medium.id:
     print("Got medium!");
  case _ when id == Difficulty.hard.id:
     print("Got hard!");
  default:
    throw Exception('Id must be 1,2 or 3');
}

But, really, I'd do:

enum class Difficulty {
  static Difficulty fromId(int id) => const {
    1: Difficulty.easy,
    2: Difficulty.medium,
    3: Difficulty.hard,
  }[id] ?? throw ArgumentError('Id must be 1,2 or 3');

  easy(1, "Easy"),
  medium(2, "Medium"),
  hard(3, "Hard");

  const Difficulty(this.id, this.designation);
  final int id;
  final String designation;
}

And then:

switch (Difficulty.fromId(json['difficultyId'])) {
  case Difficulty.easy: print("Got easy!");
  case Difficulty.medium: print("Got medium!");
  case Difficulty.hard: print("Got hard!");
}

@rubenferreira97
Copy link
Author

rubenferreira97 commented Feb 16, 2023

@munificent Really appreciate your answer, even more because this issue is closed 🙂.

To be fair I simplified the example, difficultyId is not the only field in json.

I am using this example on server side dart. I would like to pattern match multiple variables only when difficultyId maps to a valid Difficulty enum value.

You are right that this is not a pattern matching problem. Maybe #1201 (but treating when-variables like if-variables) plus pattern matching is what I am looking for, to be able to do something like this:

enum Difficulty {
  easy(1, "Easy"),
  medium(2, "Medium"),
  hard(3, "Hard");

  // I think this is a safer approach, if we add a new difficulty.
  static Difficulty? fromId(int id) => Difficulty.values.firstWhereOrNull((d) => d.id == id);

  const Difficulty(this.id, this.designation);
  final int id;
  final String designation;
}

class Game {
  final Difficulty difficulty;
  final int levels;
  final String name;
  Game(this.difficulty, this.levels, this.name);
}

void main() async {
  final json = jsonDecode('{"name": "name, "levels": 100, "difficultyId": 1}');
  if (json case {
    'name' : final String name,
    'levels' : final int levels,
    'difficultyId': final int difficultyId,
    } when (final difficulty = Difficulty.fromId(difficultyId) != null)) {
      final game = Game(difficulty, levels, name);
    // insert game and return 200 ok
   }   
   //return 400 bad request
}

I would like to avoid exceptions and handle this with checks (creating stacktraces and handle different branchs seem overkill here).

@munificent
Copy link
Member

One thing that would help here is if you could call an arbitrary conversion function in the middle of pattern matching and destructuring. F# has something called active patterns that let you do this and Scala lets you define your own extractors. I had hoped to get something similar into Dart (and initial versions of the proposal had something along those lines) but it didn't pan out. I still hope we can get it in as a last extension because as you note, they come in handy in some cases like this.

@rubenferreira97
Copy link
Author

Nice, that seems to solve this particular problem in a more generic way (allowing many more use cases). Fingers crossed and looking forward for that feature. If it happens I hope it gets a more Darty syntax tho 😄.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

2 participants