[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 14:58:29 CST 2017


I understand your point about multiple cases + the halting problem when
applied to the implementation details of switch and pattern matching.

I think where we're missing is that what I'm envisioning would be syntactic
sugar that would be de-sugared into a a line of  single-case `if-case`
statements. It would have nothing to do with matching multiple cases via
the same mechanism as `switch`.

Motivation for the change vs. a line of if-case statements is a valid
question of course :)

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

>
> On Nov 18, 2017, at 4:42 AM, Peter Kamb <peterkamb at gmail.com> wrote:
>
> 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.
>
>
> That much I understand, but there’s no motivation as to why this kind of
> change needs to occur over a line of if-case statements.  There isn’t
> anything in the pitch that demonstrates friction outside of a personal
> distaste for the syntax.
>
>
>  > > 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`.
>
>
> It is not pattern matching because it violates minimality - and we have a
> warning today when you write switches of overlapping patterns.  Consider
> the semantics of pattern matching in the context of a tree - but it just so
> happens the nodes of this tree are your patterns and the branches, actual
> branches where control flow splits along the case-matrix.  The goal is not
> to execute a find-all, the goal is to execute a find - exactly like a
> regular expression.
>
>
>  > 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:`).
>
>
> You misunderstand me.  This is the same problem as the one I brought up
> before: You are subject to the halting problem in the general case. The
> catch-all clause isn’t just noise, it’s a fundamental necessity to
> guarantee sound semantics for this particular class of switch statements
> that, according to a fundamental barrier in computer science, cannot be
> checked for exhaustiveness.
>
> On top of that, we cannot allow you to write an uncovered switch statement
> full of expression patterns because you would most-assuredly not handle all
> possible cases (suppose I extend your OptionSet with a new bit-pattern in a
> different module - your code will miscompile).
>
>
> 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/1147a163/attachment.html>


More information about the swift-evolution mailing list