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

Peter Kamb peterkamb at gmail.com
Sat Nov 18 03:42:10 CST 2017


Thanks for the review.

Motivation is essentially to provide a low-friction way to write `if case /
if case / if case` statements that are all matching on the same expression,
much in the same way that `switch` offers a way to write `if / else if /
else` statements against the same expression.

 > > Only the *first* matching case is executed. Subsequent matching cases
are not executed.
 > This is not unrelated to pattern matching, this is the expected behavior
of every pattern matching algorithm.

By that I meant that a string of `if case / if case / if case` statements,
each individually matching against the same expression, would match and
execute all 3 cases. A `switch` using the same 3 cases would execute only
the first matching case. I took the "only first case" behavior to be a
property of the *switch*, not of "pattern matching" in general? But perhaps
I'm using the wrong terminology.

 > > Allow filtering a single object through *multiple* cases of pattern
matching, executing *all* cases that match.
 > Again, this is not pattern matching.

A switch can have multiple cases that "would" match and execute, if you
were to comment out the successful case above them. Why would a
hypothetical statement that executed *all* matching cases, rather than just
the first case, not be pattern matching? It wouldn't be a `switch`, for
sure, but it seems like it would be just as much "pattern matching" as an
`if-case`.

 > This is not boilerplate!

An `if case / if case` pair would not have the same concept of a shared
`else/default` case used by a `switch` or `if/else`, which is why I said it
would not be needed. `if-case` does not require an else/default. (Perhaps
boilerplate was the wrong word, and I definitely didn't mean to suggest
that switches should no longer require `default:`).

 > A syntax that duplicates the existing if-case construct

Right, it would duplicate `if-case` just as `switch` duplicates `if-else`.

Cheers, thanks.

Peter

On Sat, Nov 18, 2017 at 12:19 AM, Robert Widmann <devteam.codafi at gmail.com>
wrote:

> Having spent a lot of time with ‘switch’, I don’t understand any of the
> motivations or corresponding justifications for this idea.  Comments inline:
>
> ~Robert Widmann
>
> 2017/11/17 15:06、Peter Kamb via swift-evolution <swift-evolution at swift.org
> >のメール:
>
> ## 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.
>
>
> This is not unrelated to pattern matching, this is the expected behavior
> of every pattern matching algorithm.  The intent is to compile a
> switch-case tree down to (conceptually) a (hopefully minimal) if-else
> tree.  You may be thinking of the behavior of switch in C and C-likes which
> is most certainly not pattern matching and includes behavior Swift has
> explicitly chosen to avoid like implicit fallthroughs.
>
>  - `default:` case is required, even for expressions where a default case
> does not make sense.
>
>
> Expression patterns may look to be covered “at first glance”, but the
> analysis required to prove that is equivalent to solving the halting
> problem in the general case.  Further, your proposed idea has absolutely
> nothing to do with this.
>
>
> 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.
>
>
> Again, this is not pattern matching.
>
>
>  - A syntax that exactly aligns with the familiar, succinct, elegant, and
> understandable `switch` syntax.
>
>
> A syntax that duplicates the existing if-case construct which, I should
> note, takes the same number of lines and roughly the same number of columns
> to express as your match.  Even less, considering you unwrap in the if-case
> to exaggerate the example but not in the match.
>
>
> - 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.)
>
>
> This is not boilerplate!
>
>
> ## 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"
> ```
>
>
> for i in 1...100 {
>   var output = “”
>   output += (i % 3 == 0) ? “Fizz” : “”
>   output += (i % 5 == 0) ? “Buzz” : “”
>   print(output.isEmpty ? “\(i)” : output)
> }
>
> If control flow should branch multiple times, *why not write just write
> it that way!*
>
> _______________________________________________
> 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/20171118/15644c69/attachment.html>


More information about the swift-evolution mailing list