arrow-kt / arrow

Ξ›rrow - Functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
6.13k stars 442 forks source link

Pattern matching DSL #3442

Closed serras closed 3 months ago

serras commented 3 months ago

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" }
}
github-actions[bot] commented 3 months ago

Kover Report

File Coverage [40.00%]
arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Combinators.kt 32.50%
arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Match.kt 60.00%
Total Project Coverage 60.80%
nomisRev commented 3 months ago

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

serras commented 3 months ago

Can we also supports sealed classes through Prism?

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

nomisRev commented 3 months ago

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

kyay10 commented 3 months ago

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 commented 3 months ago

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

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 commented 3 months ago

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!

raulraja commented 3 months ago

Late to the party but wanted to say this looks great πŸ‘