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

Pattern matching DSL #3442

Merged
merged 5 commits into from
May 29, 2024
Merged

Pattern matching DSL #3442

merged 5 commits into from
May 29, 2024

Conversation

serras
Copy link
Member

@serras serras commented May 27, 2024

This PR proposes a small DSL to describe pattern matching. This is based on three ideas:

  1. Pattern matching is essentially as attempting to match several Optionals in a row,
  2. We can build optionals that match on more than one value (that is, Optional<S, A> + Optional<S, B> = Optional<S, Pair<A, B>>
  3. The usual way to define patterns looks a lot like function application, so we can reuse invoke to simulate them.

This is an example from the code. Note that then separates an optional that matches and the code that uses the information being matched as result.

val User.name: String get() = this.match {
  // Person(Name(firstName = fn), age if it < 18)
  User.person(Person.name(Name.firstName), Person.age.suchThat { it < 18 }) then { (fn, _) -> fn }
  // Person(Name(firstName = fn, lastName = ln)) -> "Sir/Madam $fn $ln"
  User.person(Person.name(Name.firstName, Name.lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" }
  // Company(name = nm, director = Name(lastName = d))
  User.company(Company.name, Company.director(Name.lastName)) then { (nm, d) -> "$nm, att. $d" }
}

Copy link
Contributor

github-actions bot commented May 27, 2024

@nomisRev
Copy link
Member

Cool stuff! Never actually used Optics for pattern matching, knew it was theoretically possible. Can we also supports sealed classes through Prism?

@serras
Copy link
Member Author

serras commented May 28, 2024

Can we also supports sealed classes through Prism?

We do! It's there in the example :P

@nomisRev
Copy link
Member

Oh yes, of course! It's relying on Prism : Optional 😁
Wow, that is so cool! 👏 👏 👏

Copy link
Member

@nomisRev nomisRev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHIP IT!!

Comment on lines 16 to 28
public fun <S, R> S.match(cases: MatchScope<S, R>.() -> Unit): R {
val subject = this
val scope = object : MatchScope<S, R> {
override fun <A> Optional<S, A>.then(next: (A) -> R) {
this.getOrNull(subject)?.let { throw MatchFound(next(it)) }
}
override fun default(next: () -> R) {
throw MatchFound(next())
}
}
try {
cases(scope)
throw IllegalArgumentException("no match found")
Copy link
Member

@nomisRev nomisRev May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we force a default lambda, and provide a second function matchOrThrow without the default lambda?

This way it's less likely to forget to catch IllegalArgumentException, and if you have no default you are forced to be more explicit.

@kyay10
Copy link
Collaborator

kyay10 commented May 28, 2024

Haven't taken a full look at the PR yet, but this should definitely have an integration with SingletonRaise so that it fails similarly to Monad.fail from Haskell

@serras
Copy link
Member Author

serras commented May 28, 2024

I've just pushed a new commit which I think covers both of your comments:

  • The main function is now matchOrElse, which gets an additional argument telling what to do if no match is found;
  • From those matchOrThrow, matchOrRaise, and matchUnit are derived.

I like the possibility of having a catch-all case within the DSL, since it mirrors how one usually defines pattern matching.

value.match {
  User.company.address(Address.street) then { s -> ... }
  default { "Unknown address " }
}

@nomisRev
Copy link
Member

I like the possibility of having a catch-all case within the DSL, since it mirrors how one usually defines pattern matching.

Yes, that looks great, but I felt that the strategy should be explicit in the function names. Thank you, great improvement!

@serras serras merged commit c2917bd into main May 29, 2024
11 checks passed
@serras serras deleted the serras/pattern-match branch May 29, 2024 18:39
@raulraja
Copy link
Member

Late to the party but wanted to say this looks great 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants