On Sat, Nov 18, 2017 at 12:19 AM, Robert Widmann <devteam.codafi@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution