Btw, how do we check a match against multiple discriminators? @GreatApe's approach allows to put them into an array and then perform "contains", with other approaches do we just perform it one by one?
value is case .foo && value is case .bar && value is case .baz
switch's case allows matching with multiple values, so perhaps this then?
value is case .foo, .bar, .baz
on the other hand with explicit terms we could make arbitrary expressions, not necessarily conjunction.
By analogy to switch statements, I would expect value is case .foo, .bar, .baz to be equivalent to:
switch value {
case .foo, .bar, .baz:
true
default:
false
}
so it would be equivalent to:
value is case .foo || value is case .bar || value is case .baz
(I don't think it's possible for value is case .foo && value is case .bar to be true, right? value can only be one case or the other, unless I'm missing something).
That being said, I don't think we'd want to support this. One reason is that it seems ambiguous. A contrived example could be something like:
let array = [value is case .foo, .bar]
// is this an array with one value:
let array = [(value is case .foo, .bar)]
// or two values:
let array = [(value is case .foo), .bar]
// aka
let array = [(value is case .foo), Bool.bar]
I love this proposal! Pattern matching as a Boolean expression beyond the limitations of directly invoking the ~= operator has been a sore point for a long time now.
I was thinking the same thing, and I’ll take it one step further: with a proper understanding of case, I find is case to be a significantly better spelling than matches; it composes well-named features in a sensible way, that couldn’t really mean anything else. I would very much like to see if x is case .foo(let bar) too.
I've been taking a deeper look at operator precedence, and especially comparing the differences between using either CastingPrecedence (like the is operator), or the ComparisonPrecedence (like the == operator):
These result in the same groupings for most common operators:
!a is case b // (!a) is case b
a is case b || c is case d // (a is case b) || (c is case d)
a is case b && c is case d // (a is case b) || (c is case d)
a is case b ? c is case d : e is case f // (a is case b) ? (c is case d) : (e is case f)
a is case b ..< c // a is case (b ..< c)
Only these operators would be affected:
a is case b == c
a is case b == c is case d
a is case b != c is case d
a ?? b is case c
a is case b ?? c
a is case b is Bool
If is case were in ComparisonPrecedence, the above examples would be grouped as:
a is case b == c // 🛑 Error: adjacent operators are in non-associative precedence group 'ComparisonPrecedence'
a is case b == c is case d // 🛑 Error: adjacent operators are in non-associative precedence group 'ComparisonPrecedence'
(a ?? b) is case c
a is case (b ?? c)
a is case (b is Bool)
But if is case were in CastingPrecedence, the above examples would be grouped as:
(a is case b) == c
(a is case b) == (c is case d)
(a is case b) != (c is case d)
a ?? (b is case c)
(a is case b) ?? c
a is case b is Bool // 🛑 Error: adjacent operators are in non-associative precedence group 'CastingPrecedence'
Based on these examples, I think it makes sense to prefer using including is case in CastingPrecedence:
By analogy to the is operator, it seems reasonable to include is case in the same precedence group.
Enabling inline usage of the == operator seems most useful, of the above differences between the two groups. This also matches the grouping of other boolean operators like || and &&, so feels natural.
Using is and is case together in a single expression is likely to be very uncommon, so doesn't need to be prioritized.
Neither grouping of ?? seems obviously more correct than the other
Curious to hear what folks think. Is there anything else we should consider here?
tldr; Don't introduce a new special-case matching operator/syntax.
Instead, introduce a new property of enums (the base case) which can use existing matching facilities
rather than adding a new matching syntax, how about adding a way of specifying that you're talking about the base enum case and are ignoring the associated value.
using the original example
enum Destination {
case inbox
case messageThread(id: Int)
}
all enums would then have a base case. And this would be useful more generally. I could write the following code
var currentView:Destination = //something
var newView:Destination = //something else
let needToSwitchWindows = newView.baseCase == currentView.baseCase
no new syntax, no new special casing - instead, an extension of enums that let us 'talk about' what their base case is.
This is essentially a language-provided version of the 'parallel enum with no associated values' solution discussed in the original post.
I'm not wedded to the syntax here, there is probably a better way of expressing this - but the idea of exposing a simple way of expressing 'what is the base of this enum' seems powerful to me.
I'm sorry for leaving you hanging for so long. The Language Steering Group talked about this proposal. We agree that there's a lot of room for improvement in this part of the language, and this specific proposal seems to be high-quality: it is thorough and well thought out. Nevertheless, we don't feel comfortable running proposals in the general space of pattern matching at the moment, because we don't feel like we have a good understanding of where the language ought to be going. We therefore want to be cautious about adding features that feel like small, incremental "wins", because we don't want to end up with three or four different features that each in isolation make some specific use case easier but which collectively don't work as a cohesive whole.
Ultimately, we'd like to have a vision document for the future of pattern matching in Swift. Right now, we're not looking to invite anyone to write that document, but we'd be very interested in having a long-running conversation about it, ideally covering a number of different questions in the area:
a lightweight way to check whether an enum value is in a particular case (maybe like this, or maybe with a member syntax)
the above, but allowing the extraction of the case's payload
the above, but allowing a match against an arbitrary pattern
whether bindings can be supported in such patterns
whether we can/should improve pattern syntax in general, e.g. to avoid case let
some equivalent to KeyPaths for enum cases
some surfacing of enum cases in normal KeyPaths (presumably with optional type)
whether any of these are reasonable to address just with macros
probably other things that don't come to mind immediately
We're not saying that all of these need to be addressed by a single proposal! And maybe it's okay to say that some of them aren't worth adding new features for and should just be done with switch. But we don't feel like we've seen enough discussion which takes on these issues holistically.
Therefore, we'd like to invite interested folks to start a new discussion thread about pattern matching in general.
I'm hopeful for this one. One thing I'd love to see in Swift is the ability to add let bindings to more types of patterns.
For example, by overloading the ~= operator, we can define arbitrary pairs of pattern*value that would be matched by a case statement, but in these, we cannot add bindings to extract values from patterns.
Ultimately, we'd like to have a vision document for the future of pattern matching in Swift. Right now, we're not looking to invite anyone to write that document
Seems like this was the next step, but I’m not sure if another discussion ever got started. Given their work in the space, I would assume that @stephencelis and/or @mbrandonw might know?
@John_McCall , if possible, I would like to start working on a long-term vision for pattern matching. Would you please offer some advice on where to start with this?
I believe long-term vision documents are authored by members of the core team. We'd be happy to chat with anyone that takes up the cause if we could be of help, though!
The LSG generally invites people to write vision documents; it’s not a volunteer system. That said, we’d be happy to see someone lead a broad discussion about where pattern matching should go. I could see us inviting someone from that discussion to write a vision, if they had the time and motivation to try to carry that vision out in the implementation. That last point is very important, though. A vision document is supposed to lay out the design for work that’s actually being done, fitting it into a broader vision for the future design of that language area. It’s okay (even good) to cover future directions that aren’t going to be immediate priorities and try to imagine how they’ll fit in, but if nobody’s going to work on this at all, it’s just not a very good use of anyone’s time (and somewhat inconsiderate to our future selves) to sign off on the architecture of a castle in the sky.