-
Notifications
You must be signed in to change notification settings - Fork 363
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 #213
Pattern matching #213
Conversation
…coroutines example
…to talk about matching on collections
… tuples and fix bug in Jake Wharton example
With |
I purposefully aimed to not introduce a new keyword (like |
Co-authored-by: Nicola Corti <[email protected]>
Co-authored-by: Nicola Corti <[email protected]>
Co-authored-by: Nicola Corti <[email protected]>
Good remark. I think the expected behaviour would be to only call each deconstructor once. The main case for this is avoiding an extra function call every time and benefitting from smart casts as usual. I don't see a scenario where the alternative would be more desirable. |
Here's a really short personal review of this proposal (disclaimer: it does not necessarily represent the opinion of the Kotlin team on the pattern matching): 👍 Being able to combine type test and guard expression in 👉 Use of positional destructuring needs a careful review from a tooling perspective. 👎 Introducing a new |
I would suggest to go with when(val item = getItem()){
is Book -> ...
is Bottle -> ...
if(item.canBreak()) -> ...
// or potentially
if item.canBreak ->
else -> ...
} Now, if you have a long list of
Yeah, this would be weird behaviour. I thought we were planning to use |
Thanks for your inputs, @elizarov , @Kroppeb !
I am not married to this syntax, and would also be completely fine with moving it into a new proposal. I do believe though that both guards and pattern matching in general should be designed closely in consideration of each other so they are nice to use if both ever make it into the language.
I agree, and it was also discussed in Slack that Kotlin could potentially suffer from matches like Potentially, this could be solved by matching on named properties. The proposal quickly takes a look at the idea but could not come up with nice syntax. Suggestions are welcome!
This concern was raised early when this proposal was submitted, and I think 2 solutions exist: explicitly declaring new variables through This is discussed in the Design Decisions section (in particular here). I am personally against having to type In examples: val age = 2
when (person) {
is Person(_, age) -> ...
... would not be valid, much like one cannot write: val age = 3
...
val age = 2.3 I would like to note that this is at present valid Kotlin: val a : String.() -> Unit = {
val length = 3.0f
val x : Float = length
} In short, the destructuring this proposal suggests would be similar to the semantics of delcaring something new through
I fail to see how this syntax combines a type test and a guard. Could you please provide another example? For example, an equivalent of: when(val x = getSomeObject()) {
is Bottle where { it.canBreak() } -> // x is a breakable Bottle
is Bottle -> // x is an unbreakable Bottle
is Book -> // x is not a bottle at all
else ->
} where only a Or did I misunderstand and you meant something along the lines of: when(val x = getSomeObject()) {
is Bottle if (x.canBreak()) -> // x is a breakable Bottle
else -> // x is an unbreakable Bottle
is Book -> // x is not a bottle at all
else ->
} If this is the case, I do personally like not having to write the type test for each match. It also acts more like Haskell pattern matching: a type match is performed, then a series of guards, then the next type match. |
The example I write was not about pattern matching. when(val x = getSomeObject()) {
is Bottle if (x.canBreak()) -> // x is a breakable Bottle
is Bottle -> // x is an unbreakable Bottle
is Book -> // x is not a bottle at all
else ->
} However what I was talking about was having the type test optional and just having a guard alone also be valid, so you could rewrite the following val x = getSomeObject()
when{
x is Book -> // use book
isTrash(x) -> // throw away
x is Chair -> // use chair
x is Table -> // use table
} into when(val x = getSomeObject()){
is Book -> // use book
if(isTrash(x)) -> // throw away
is Chair -> // use chair
is Table -> // use table
} |
As per @elizarov 's and @Kroppeb 's suggestions, I decided to include an alternative The same goes for other 'alternatives' of the proposal (like writing nested Personally, I am happy with this suggestion as it allows chained guards, and looks more
Additionally, I think this proposal is mainly concerned with pattern matching and this suggestion |
Relating to the original proposal: val result = when(download) {
is App(name, Person("Alice", _)) -> "Alice's app $name"
is Movie(title, Person("Alice", _)) -> "Alice's movie $title"
is App, Movie -> "Not by Alice"
} This probably won't work as expected, because name and title variables are just read from outer scope. Full example: val name = "someName"
val title = "someTitle"
val result = when(download) {
is App(name, Person("Alice", _)) -> "Alice's app $name"
is Movie(title, Person("Alice", _)) -> "Alice's movie $title"
is App, Movie -> "Not by Alice"
} To indicate that we want to extract data and not just match variable from outer scope we should somehow declare "new" variables. For example like this: val result = when(download) {
is App(val name, Person("Alice", _)) -> "Alice's app $name"
is Movie(val title, Person("Alice", _)) -> "Alice's movie $title"
is App, Movie -> "Not by Alice"
} Another thing I don't like in current implementation is how declared subject variable is just smart-casted, but you can't declare new variable name for each case. This is how I would like it to work more less: val result = when(download) {
is val app: App -> "App: $app"
is val movie: Movie -> "Movie: $movie"
} or without val keyword: val result = when(download) {
is app: App -> "App: $app"
is movie: Movie -> "Movie: $movie"
} |
Hi @Wlodas !
As defind in the Semantics section, this is a compile error (conflicting declarations). It's clear and will avoid unexpected mistakes.
Discussed as an alternative to current syntax.
I assume that waht you want is to 'name' a match. This is briefly discussed here, and is called an as-pattern or binder. In the example you provide, you don't even extract new variables, you simply rename the |
The Java pattern matching proposal has recently evolved, if you're interested for inspiration: |
Do you think For instance, suppose there's a regexp for version parsing:
it would be nice to "pattern match" regexp outcomes to |
Hi @vlsi I think that, as the proposal stands right now, something like this would be possible: val pattern = """(?<major>\d+).(?<minor>\d+)(?:.(?<patch>\d+))"""
val comment = when (pattern.toRegex().matchEntire("1.2.3")?.destructured) {
null -> "not a match!"
is ("0", _, _) -> "some experimental stuff"
is (major, minor, patch) -> "major version number is $major"
} But specifics (like matching on something nullable and smart casting afterwards, syntax) would need to be discussed some more. Let me know what you think and whether this is what you had in mind. |
I would like if the matching was implemented For instance: val pattern = """(?<major>\d+).(?<minor>\d+)(?:.(?<patch>\d+))"""
val (major: Int, minor: Int, patch: Int?) by pattern.toRegex().matchEntire("1.2.3")
// The order should not matter, so the regex groups should be recognized by name
val (minor: Int, patch: Int?, major: Int) by pattern.toRegex().matchEntire("1.2.3") |
Matching by name is currently not part of the proposal. I feel like what you suggested here
...has to do more with tying delegates and destructuring, rather than matching inside of |
What do you think if typing inside For instance: val pattern = """(?<major>\d+).(?<minor>\d+)(?:.(?<patch>\d+))"""
when (pattern.toRegex().matchEntire("1.2.3")) {
// Named destructuring
(major: Int, minor: Int, patch: Int?) -> ...
// constructor-like destructuring
MatchResult(_) -> w
else -> null
} |
We discussed this a bit with @rnett but we could not come up with nice syntax. What you propose
is fine for destructuring only, but how would you write that for a nested pattern? For example a Additionally, I'm not sure we could destructure regex by name: you'd still not have properties named like the groups in the regex. I suggest you make a PR detailing how you envision that with added details :) |
FYI: This would be the Swift code, which also uses struct Person: Equatable {
let name: String
}
enum Download: Equatable {
case app(name: String, developer: Person)
case movie(title: String, director: Person)
}
let download: Download = .app(name: "App", developer: Person(name: "Alice"))
switch download {
case .app(let name, Person(name: "Alice")):
print("Alice's app \(name)")
case .movie(let title, Person(name: "Alice")):
print("Alice's movie \(title)")
default:
print("Not by Alice")
} You can use case .movie(let title, let person):
print("\(person.name)'s movie \(title)")
// you can move the let to the front
case let .movie(title, person):
print("\(person.name)'s movie \(title)")
// using with another where clause
case let .movie(title, person) where title.count <= 3:
print("\(person.name)'s movie with a short title: \(title)") |
I've reopened https://youtrack.jetbrains.com/issue/KT-186 language design request for Pattern Matching and added a "wish list" for features and qualities that a good Kotlin-style design for pattern matching should strive to achieve. That issue might be a better place to discuss what pattern matching in Kotlin should and should not do since it does not propose any particular design, but just establishes some basics and calls for a more open-ended discussion, consistently with the Contribution Guidelines for the Kotlin language as spelled out in this KEEP's readme. |
Thanks for re-opening @elizarov I had been meaning to discuss this proposal's shortcomings (in particular nominal destructuring). I posted over at https://youtrack.jetbrains.com/issue/KT-186 |
@elizarov Java 17 is officially getting pattern matching support in switch: |
Let's move all further discussion to https://youtrack.jetbrains.com/issue/KT-186 Closing this one. |
Proposal
The goal of this proposal is to bring full blown pattern matching to Kotlin.
Pattern matching is a tried and tested feature of many languages, such as Haskell, Rust, Scala, Ruby, and in the near future, Java.
In a nutshell, it is a quick and intuitive way of checking whether an object is of some type, and to extract and check its contents. The propsal aims to do this through the existing
is
and destructuringcomponentN()
semantics.Pattern matching is a major addition to a language, and as such several aspects of the proposal are up to debate. Discussion and contributions are welcome.
Here is a short example of what Kotlin with pattern matching could look like:
For more details, you can read a rendered version of the proposal here.