## 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@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution