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

Peter Kamb peterkamb at gmail.com
Fri Nov 17 14:05:32 CST 2017


## 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"
```
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171117/2b234824/attachment.html>


More information about the swift-evolution mailing list