[swift-evolution] [Idea] [Pitch] Add `match` statement as `switch`-like syntax alternative to `if case` pattern matching

Robert Widmann devteam.codafi at gmail.com
Sun Nov 19 00:37:40 CST 2017


In regards to the problem in the root (using if-cases to do membership tests on option sets), the code given there involving thinking of the OptionSet as a sequence and iterating over it makes this far less likely to happen.  I think this is a pattern that doesn't need syntax, it needs stdlib support at most.  Perhaps an extension to OptionSet when the RawValue is known to be a sized integral type that does just that would clean up here.

~Robert Widmann 

2017/11/18 17:25、Benjamin G via swift-evolution <swift-evolution at swift.org>のメール:

> I think because it's not immediately obvious with multiple if statement, that they all try to compare the same expression to different patterns.
> 
> match exp {
> case 1
> case 2
> }
> vs
> if case 1 = exp
> if case 2 = anotherexp 
> 
> 
>> On Sat, Nov 18, 2017 at 10:43 PM, Xiaodi Wu via swift-evolution <swift-evolution at swift.org> wrote:
>>> On Sat, Nov 18, 2017 at 3:12 PM, Peter Kamb <peterkamb at gmail.com> wrote:
>> 
>>> A high bar for new syntax is fair and expected, and by posting I was hoping to maybe find an alternative in the comments here.
>>> 
>>> But AFAIK there's currently no ability in Swift to:
>>> 
>>> "Evaluate a *single* control expression against all of these patterns, and execute any and all cases that match"
>>> 
>>> Multiple `if-case` statements, each re-stating the control expression, are ok.
>>> 
>>> But that's definitely not as clear or concise as a switch-like construct with the single control expression at the top. Or perhaps some other alternative such as the mentioned `continue` or somehow enumerating a set of cases.
>> 
>> You're simply restating your proposed new syntax as the thing that's missing. But what is the use case that motivates this construct? In what way are multiple if-case statements "not as clear"?
>> 
>>>> On Sat, Nov 18, 2017 at 11:18 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>> Robert is quite right--I'm not sure what we're designing for here. There's a very high bar for introducing new syntax and a distaste for the existing syntax is not a motivating use case.
>>>> 
>>>> 
>>>>> On Sat, Nov 18, 2017 at 12:53 PM, Kevin Nattinger via swift-evolution <swift-evolution at swift.org> wrote:
>>>>> There have been earlier suggestions for an alternative to `fallthrough` that would continue matching cases; I think that is much more likely to get support than a whole new construct with only a subtle difference from an existing one—would that be an acceptable alternative to you?
>>>>> 
>>>>> > On Nov 17, 2017, at 12:06 PM, Peter Kamb via swift-evolution <swift-evolution at swift.org> wrote:
>>>>> >
>>>>> > ## Title
>>>>> >
>>>>> > Add `match` statement as `switch`-like syntax alternative to `if case` pattern matching
>>>>> >
>>>>> > ## Summary:
>>>>> >
>>>>> > The syntax of the `switch` statement is familiar, succinct, elegant, and understandable. Swift pattern-matching tutorials use `switch` statements almost exclusively, with small sections at the end for alternatives such as `if case`.
>>>>> >
>>>>> > However, the `switch` statement has several unique behaviors unrelated to pattern matching. Namely:
>>>>> >
>>>>> >  - Only the *first* matching case is executed. Subsequent matching cases are not executed.
>>>>> >  - `default:` case is required, even for expressions where a default case does not make sense.
>>>>> >
>>>>> > These behaviors prevent `switch` from being used as a generic match-patterns-against-a-single-expression statement.
>>>>> >
>>>>> > Swift should contain an equally-good pattern-matching statement that does not limit itself single-branch switching.
>>>>> >
>>>>> > ## Pitch:
>>>>> >
>>>>> > Add a `match` statement with the same elegant syntax as the `switch` statement, but without any of the "branch switching" baggage.
>>>>> >
>>>>> > ```
>>>>> > match someValue {
>>>>> > case patternOne:
>>>>> >     always executed if pattern matches
>>>>> > case patternTwo:
>>>>> >     always executed if pattern matches
>>>>> > }
>>>>> > ```
>>>>> >
>>>>> > The match statement would allow a single value to be filtered through *multiple* cases of pattern-matching evaluation.
>>>>> >
>>>>> > ## Example:
>>>>> >
>>>>> > ```
>>>>> > struct TextFlags: OptionSet {
>>>>> >     let rawValue: Int
>>>>> >     static let italics = TextFlags(rawValue: 1 << 1)
>>>>> >     static let bold    = TextFlags(rawValue: 1 << 2)
>>>>> > }
>>>>> >
>>>>> > let textFlags: TextFlags = [.italics, .bold]
>>>>> >
>>>>> >
>>>>> >
>>>>> > // SWITCH STATEMENT
>>>>> > switch textFlags {
>>>>> > case let x where x.contains(.italics):
>>>>> >     print("italics")
>>>>> > case let x where x.contains(.bold):
>>>>> >     print("bold")
>>>>> > default:
>>>>> >     print("forced to include a default case")
>>>>> > }
>>>>> > // prints "italics"
>>>>> > // Does NOT print "bold", despite .bold being set.
>>>>> >
>>>>> >
>>>>> >
>>>>> > // MATCH STATEMENT
>>>>> > match textFlags {
>>>>> > case let x where x.contains(.italics):
>>>>> >     print("italics")
>>>>> > case let x where x.contains(.bold):
>>>>> >     print("bold")
>>>>> > }
>>>>> > // prints "italics"
>>>>> > // prints "bold"
>>>>> > ```
>>>>> >
>>>>> > ## Enum vs. OptionSet
>>>>> >
>>>>> > The basic difference between `switch` and `match` is the same conceptual difference between `Emum` and an `OptionSet` bitmask.
>>>>> >
>>>>> > `switch` is essentially designed for enums: switching to a single logical branch based on the single distinct case represented by the enum.
>>>>> >
>>>>> > `match` would be designed for OptionSet bitmasks and similar constructs. Executing behavior for *any and all* of the following cases and patterns that match.
>>>>> >
>>>>> > The programmer would choose between `switch` or `match` based on the goal of the pattern matching. For example, pattern matching a String. `switch` would be appropriate for evaluating a String that represents the rawValue of an enum. But `match` would be more appropriate for evaluating a single input String against multiple unrelated-to-each-other regexes.
>>>>> >
>>>>> > ## Existing Alternatives
>>>>> >
>>>>> > `switch` cannot be used to match multiple cases. There are several ways "test a value against multiple patterns, executing behavior for each pattern that matches", but none are as elegant and understandable as the switch statement syntax.
>>>>> >
>>>>> > Example using a string of independent `if case` statements:
>>>>> >
>>>>> > ```
>>>>> > if case let x = textFlags, x.contains(.italics) {
>>>>> >     print("italics")
>>>>> > }
>>>>> >
>>>>> > if case let x = textFlags, x.contains(.bold) {
>>>>> >     print("bold")
>>>>> > }
>>>>> > ```
>>>>> >
>>>>> > ## `match` statement benefits:
>>>>> >
>>>>> >  - Allow filtering a single object through *multiple* cases of pattern matching, executing *all* cases that match.
>>>>> >
>>>>> >  - A syntax that exactly aligns with the familiar, succinct, elegant, and understandable `switch` syntax.
>>>>> >
>>>>> > - The keyword "match" highlights that pattern matching will occur. Would be even better than `switch` for initial introductions to pattern-matching.
>>>>> >
>>>>> >  - No need to convert between the strangely slightly different syntax of `switch` vs. `if case`, such as `case let x where x.contains(.italics):` to `if case let x = textFlags, x.contains(.italics) {`
>>>>> >
>>>>> >  - Bring the "Expression Pattern" to non-branch-switching contexts. Currently: "An expression pattern represents the value of an expression. Expression patterns appear only in switch statement case labels."
>>>>> >
>>>>> >  - A single `match controlExpression` at the top rather than `controlExpression` being repeated (and possibly changed) in every single `if case` statement.
>>>>> >
>>>>> >  - Duplicated `controlExpression` is an opportunity for bugs such as typos or changes to the expression being evaluated in a *single* `if case` from the set, rather than all cases.
>>>>> >
>>>>> >  - Reduces to a pretty elegant single-case. This one-liner is an easy "just delete whitespace" conversion from standard multi-line switch/match syntax, whereas `if case` is not.
>>>>> >
>>>>> > ```
>>>>> >  match value { case pattern:
>>>>> >     print("matched")
>>>>> > }
>>>>> > ```
>>>>> >
>>>>> >  - Eliminate the boilerplate `default: break` case line for non-exhaustible expressions. Pretty much any non-Enum type being evaluated is non-exhaustible. (This is not the *main* goal of this proposal.)
>>>>> >
>>>>> > ## Prototype
>>>>> >
>>>>> > A prototype `match` statement can be created in Swift by wrapping a `switch` statement in a loop and constructing each case to match only on a given iteration of the loop:
>>>>> >
>>>>> > ```
>>>>> > match: for eachCase in 0...1 {
>>>>> > switch (eachCase, textFlags) {
>>>>> > case (0, let x) where x.contains(.italics):
>>>>> >     print("italics")
>>>>> > case (1, let x) where x.contains(.bold):
>>>>> >     print("bold")
>>>>> > default: break }
>>>>> > }
>>>>> >
>>>>> > // prints "italics"
>>>>> > // prints "bold"
>>>>> > ```
>>>>> >
>>>>> > ## Notes / Discussion:
>>>>> >
>>>>> > - Other Languages - I've been unable to find a switch-syntax non-"switching" pattern-match operator in any other language. If you know of any, please post!
>>>>> >
>>>>> > - Should `match` allow a `default:` case? It would be easy enough to add one that functioned like switch's default case: run if *no other* cases were executed. But, conceptually, should a "match any of these patterns" statement have an else/default clause? I think it should, unless there are any strong opinions.
>>>>> >
>>>>> > - FizzBuzz using proposed Swift `match` statement:
>>>>> >
>>>>> > ```
>>>>> > for i in 1...100 {
>>>>> >     var output = ""
>>>>> >     match 0 {
>>>>> >     case (i % 3): output += "Fizz"
>>>>> >     case (i % 3): output += "Buzz"
>>>>> >     default:      output = String(i)
>>>>> >     }
>>>>> >
>>>>> >     print(output)
>>>>> > }
>>>>> >
>>>>> > // `15` prints "FizzBuzz"
>>>>> > ```
>>>>> > _______________________________________________
>>>>> > swift-evolution mailing list
>>>>> > swift-evolution at swift.org
>>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> 
>>> 
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171119/8a347015/attachment.html>


More information about the swift-evolution mailing list