|
| 1 | +# Simplifying `guard case`/`if case` syntax |
| 2 | + |
| 3 | +* Proposal: TBD |
| 4 | +* Author: [Erica Sadun](https://github.com/erica) |
| 5 | +* Status: TBD |
| 6 | +* Review manager: TBD |
| 7 | + |
| 8 | +## Introduction |
| 9 | + |
| 10 | +This proposal re-architects `guard case` and `if case` grammar for unwrapping complex enumerations. It drops the `case` keyword from `if` and `guard`, replaces `=` with `~=`, and introduces the `:=` operator that combines declaration with assignment. |
| 11 | + |
| 12 | +Swift-evolution thread: |
| 13 | +[\[Pitch\] Reimagining `guard case`/`if case`](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html) |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +Swift's `guard case` and `if case` design aligns statement layout with the `switch` statement: |
| 18 | + |
| 19 | +```swift |
| 20 | +switch value { |
| 21 | + case let .enumeration(embedded): ... |
| 22 | +} |
| 23 | + |
| 24 | +if case let .enumeration(embedded) = value |
| 25 | +``` |
| 26 | + |
| 27 | +This grammar unifies the two approaches and offers an overall conceptual "win". However, real-world users do not think about this parallel construction or naturally connect the two layouts. |
| 28 | + |
| 29 | +* `guard case` and `if case` look like assignment statements but they are *not* assignment statements. This violates the [principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment). |
| 30 | +* In `switch`, a `case` is followed by a colon, not an assignment operator. |
| 31 | +* Swift has a pattern matching operator (`~=`) that isn't used here. |
| 32 | +* `case` syntax is wordy, including `case`, `=`, and optionally `let`/`var` assignment. |
| 33 | + |
| 34 | +`guard case` and `if case` perform simultaneous pattern matching and conditional binding. These examples demonstrate their use for a simple one-associated-value enumeration: |
| 35 | + |
| 36 | +```swift |
| 37 | +enum Result<T> { case success(T), error(Error) } |
| 38 | + |
| 39 | +// valid Swift |
| 40 | +guard case let .success(value) = result |
| 41 | + else { ... } |
| 42 | +guard case .success(let value) = result |
| 43 | + else { ... } |
| 44 | + |
| 45 | +// valid Swift |
| 46 | +if case .success(let value) = result { ... } |
| 47 | +if case let .success(value) = result { ... } |
| 48 | +``` |
| 49 | + |
| 50 | +The status quo for the `=` operator is iteratively built up in this fashion: |
| 51 | + |
| 52 | +* `=` performs assignment |
| 53 | +* `let x =` performs binding |
| 54 | +* `if let x =` performs conditional binding on optionals |
| 55 | +* `if case .foo(let x) = ` and `if case let .foo(x) =` performs conditional binding on enumerations *and* applies pattern matching |
| 56 | + |
| 57 | +Using `if case`/`guard case` in the absense of conditional binding duplicates basic pattern matching with less obvious meaning. These two statements are functionally identical: |
| 58 | + |
| 59 | +```swift |
| 60 | +if range ~= myValue { ... } // simpler |
| 61 | +if case range = myValue { ... } // confusing |
| 62 | +``` |
| 63 | + |
| 64 | +## Detailed Design |
| 65 | + |
| 66 | +This proposal replaces the current syntax with a simpler grammar that prioritizes pattern matching but mirrors basic conditional binding. The new syntax drops the `case` keyword and replaces `=` with `~=`. The results look like this: |
| 67 | + |
| 68 | +```swift |
| 69 | +guard let .success(value) ~= result else { ... } |
| 70 | +guard .success(let value) ~= result else { ... } |
| 71 | +if let .success(value) ~= result { ... } |
| 72 | +if .success(let value) ~= result { ... } |
| 73 | +guard let x? ~= anOptional else { ... } |
| 74 | +if let x? ~= anOptional { ... } |
| 75 | +``` |
| 76 | + |
| 77 | +The design includes Swift's current `let`-placement flexibility and `let`-`var` mix-and-match placement. Users may choose to use `var` instead of `let` to bind to a variable instead of a constant. In this design: |
| 78 | + |
| 79 | +* The `case` keyword is subsumed into the (existing) pattern matching operator |
| 80 | +* The statements adopt the existing `if-let`/`if var` and `guard-let`/`guard var` syntax, including `Optional` syntactic sugar. |
| 81 | + |
| 82 | +```swift |
| 83 | +if let x = anOptional { ... } // current |
| 84 | +if case let x? = anOptional { ... } // current, would be removed |
| 85 | + |
| 86 | +if let x? ~= anOptional { ... } // proposed replacement for `if case` |
| 87 | +``` |
| 88 | + |
| 89 | +Introducing a further new `:=` "declare and assign" operator eliminates the need for explicit `let`: |
| 90 | + |
| 91 | +```swift |
| 92 | +guard .success(value) := result else { ... } // clean and elegant |
| 93 | +if .success(value) := result { ... } // clean and elegant |
| 94 | +guard x? := anOptional else { ... } // newly legal, although unnecessary |
| 95 | +``` |
| 96 | + |
| 97 | +Assignments to variables require the `var` keyword, and `let` will be permitted even if it is not required, enabling coders to clarify the distinct roles in mix-and-match pattern matching: |
| 98 | + |
| 99 | +```swift |
| 100 | +guard .pair(value1, var value2) := result else { ... } // implied let |
| 101 | +guard .pair(let value1, var value2) := result else { ... } // explicit let |
| 102 | +if .success(var value) := result { ... } // variable assignment |
| 103 | +guard var x? := anOptional else { ... } // variable assignment |
| 104 | +guard var x := anOptional else { ... } // simpler variable assignment |
| 105 | +guard var x = anOptional else { ... } // even simpler (current) variable assignment |
| 106 | +guard x := anOptional else { ... } // new constant assignment |
| 107 | +``` |
| 108 | + |
| 109 | +Pattern matching without conditional binding simplifies to a standalone Boolean condition clause. On adopting this syntax, the two identical range tests naturally unify to this single version: |
| 110 | + |
| 111 | +```swift |
| 112 | +if range ~= myValue { ... } // before |
| 113 | +if case range = myValue { ... } // before |
| 114 | + |
| 115 | +if range ~= myValue { ... } // after |
| 116 | +``` |
| 117 | + |
| 118 | +### Excluded from this proposal |
| 119 | + |
| 120 | +This proposal does not address `switch case` or `for case`. |
| 121 | + |
| 122 | +## Impact on Existing Code |
| 123 | + |
| 124 | +This proposal is breaking and would require migration. |
| 125 | + |
| 126 | +## Alternatives Considered |
| 127 | + |
| 128 | +* Leaving the grammar as-is, albeit confusing |
| 129 | +* Retaining `case` and replacing the equal sign with `~=` (pattern matching) or `:` (to match the switch statement). |
0 commit comments