[Discussion] Simplifying case syntax


(Erica Sadun) #1

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator


Automatically derive properties for enum cases
(Jaden Geller) #2

I suggest you split this into 2 separate proposals. The second part seems much, much more controversial than the first.

Cheers,
Jaden Geller

···

On Feb 28, 2017, at 11:01 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #3

I agree that the ambiguity created by moving `let` outside the local binding context is problematic. I alway place `let` immediately alongside the binding for this reason.

In design 2 do you disallow matching a value using an existing name? If so, how do users match values bound to an existing name? Or is that just not possible? I would oppose design 2 if it’s not possible.

Both syntax designs you propose are very concise, but they look like an operator which can take any value with the appropriate type on the left hand side. Unfortunately this isn’t the case (haha). I think that is problematic. Did you consider this? If so, what is the rationale for this choice?

For example, a user might expect to be able to say:

// match is a boolean that is true if the pattern matched and fast otherwise
let match = .success(let value) ~= result

// we don’t know if `value` is bound here so we cannot allow the above to be valid code.

···

On Feb 28, 2017, at 1:01 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daniel Leping) #4

I like the ~> operator, but not := operator. It makes the language
inconsistent. Then we should allow using it without "if" just like this:
mylet := "hey there"

I think we should keep let as it was and avoid :=

P.S.: it also looks too much Pascal-ish :slight_smile:

···

On Tue, 28 Feb 2017 at 21:02 Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance
(eliminating edge case errors by adopting at-site conditional binding) and
one of style (using the pattern match operator consistently). Its
discussion was deferred from Phase 1 and remains in a fairly early stage.
Your feedback will help me decide whether this is a proposal I want to keep
developing or one that I should set aside and focus on other matters. Thank
you. -- E

The work-in-progress gist is here:
https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

   - Proposal: TBD
   - Author: Erica Sadun <https://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>
Introduction

This proposal re-architects case syntax grammar to reduce potential errors
and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>
Motivation

In its current design, Swift case binding suffers from two weaknesses.

   - Mixed external and internal let/var binding may introduce errors
   from uncommon edge cases.
   - Real-world users may not consider the parallel construction between if
   case/guard case with switchstatements or naturally connect the two
   layouts.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal
Case Binding

When pattern matching, it's common to bind a variable or constant. It's
uncommon but legal to use a bound value as an argument. Adopting an "always
explicit, always within the parentheses" rule adds consistency and safety
to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated valuesenum Value<T> { case one(T), two(T, T), three(T, T, T) }
// An example with two associated valueslet example2: Value<Character> = .two("a", "b")
// A bound symbollet oldValue = "x"

This code's goal is to conditionally bind newValue and pattern match the
value stored in the oldValue symbol. The first example succeeds. The
second example compiles and runs but does not match the coder's intent.
Using an external letcreates a new oldValue shadow instead of pattern
matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

In-parenthesis binding avoids accidental shadowing. It eliminates this
class of error by adding let and var key words to each use point. This
creates longer call sites but enumerations rarely contain more than three
or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if
case let and if case var (plus case varand case let) may look like single
compound keywords rather than a combination of two distinct actions to
developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern
Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement,
moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}
if case .enumeration(let embedded) = value

The status quo for the = operator is iteratively built up in this fashion:

   - = performs assignment
   - let x = performs binding
   - if let x = performs conditional binding on optionals
   - if case .foo(let x) = performs conditional binding on enumerations
   *and* applies pattern matching

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:

if range ~= myValue { ... } // simplerif case range = myValue { ... } // confusing

Issues with the current design include:

   - guard case and if case look like standard non-conditional assignment
   statements but they are *not* assignment statements. Using the
   assignment operator violates the principle of least astonishment
   <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
   - In switch, a case is followed by a colon, not an assignment operator.
   - Swift *has* a pattern matching operator (~=) but does not use it
   here.
   - case syntax is wordy. The statement includes case, =, and optionally
   let/var conditional binding. Design alternatives could streamline this
   syntax, enhance clarity, and introduce a more concise format.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed
Design

This proposal adopts point-of-use conditional binding and recommends one
of the following designs. A successful design will replace the current
syntax with a simpler grammar that prioritizes pattern matching and support
conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design
1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results
look like this, showcasing a variety of letplacement, variable binding,
and optional sugar alternatives.

guard .success(let value) ~= result else { ... }guard .success(var value) ~= result else { ... }if .success(let value) ~= result { ... }if .success(var value) ~= result { ... }guard let x? ~= anOptional else { ... }if let x? ~= anOptional { ... }

In this design:

   - The case keyword is subsumed into the (existing) pattern matching
   operator
   - The statements adopt the existing if-let/if var and guard-let/guard
   var syntax, including Optionalsyntactic sugar.

if let x = anOptional { ... } // currentif case let x? = anOptional { ... } // would be removedif let x? ~= anOptional { ... } // proposed replacement for `if case`

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:

if range ~= myValue { ... } // beforeif case range = myValue { ... } // beforeif range ~= myValue { ... } // after

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design
2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This
operator eliminates the need for explicit let, although the keyword is
allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... } guard .success(let value) := result else { ... }if .success(value) := result { ... }if .success(let value) := result { ... }guard value? := anOptional else { ... } // newly legal, although unnecessaryguard let value? := anOptional else { ... } // newly legal, although unnecessary

Assignments to variables require the var keyword, enabling coders to
clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied letguard .pair(let value1, var value2) := result else { ... } // explicit letif .success(var value) := result { ... } // variable assignmentguard var x? := anOptional else { ... } // variable assignmentguard var x := anOptional else { ... } // simpler variable assignmentguard var x = anOptional else { ... } // even simpler (current) variable assignmentguard x := anOptional else { ... } // new constant assignment

Adopting this syntax provides more natural results for binding associated
enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded
from this proposal

This proposal does not address switch case or for case beyond internal
binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact
on Existing Code

This proposal is breaking and would require migration. External let or var would
automatically be moved by fixits into use points. Current guard case and if
case syntax would be migrated to the new design.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>
Timeline

Although removing if case and guard case are breaking, this proposal
should wait until Swift 4 Stage two to allow proper debate and
consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives
Considered

   - Leaving the grammar as-is, albeit confusing
   - Retaining case and replacing the equal sign with ~= (pattern
   matching) or : (to match the switch statement).
   - Adding matches or is as an alternative to the pattern matching
   operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Hart) #5

I’m happy that someone is trying to fix this small wart in the language. I’ve always wanted something like Design 1. It makes sense because we are already used to the pattern matching operator and we finally fix the inconsistencies between if/guard and switch.

···

On 28 Feb 2017, at 20:01, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pyry Jahkola) #6

Erica,

Instead of going into all these lengths introducing new syntax, why not simply turn it into a warning when a `case let` binding shadows an existing variable with the exact same type (which supposedly also conforms to Equatable)?

Examples, including how to silence the said warning:

    enum Value<T> { case one(T), two(T, T), three(T, T, T) }
    
    let example: Value<String> = .two("a", "b")
    let oldValue = "x"
    // (Besides, you probably intended `oldValue` to be a `Character` in your
    // example. Well, it's a `String` in mine.)
    
    if case let .two(newValue, oldValue) = example { ... }
    // ~~~~~~~~
    // warning: 'oldValue' shadows an existing variable of same type 'String'
    
    if case let .two(newValue, (oldValue)) = example { assert(oldValue == "b") }
    // Ok, adding extra parentheses silences the warning.

    if case let .one(example) = example { ... }
    // Ok, because there's no way the author would equate the `example: String`
    // in the LHS to the `example: Value<String>` of the RHS.
    
    let maybe: Optional = "perhaps"
    if case let maybe? = maybe { ... }
    if case let .some(maybe) = maybe { ... }
    // Again, none of these examples would be affected by the warning, because
    // the `maybe: String` bound in the `case let` has a different type than
    // the `maybe: String?` in the outer scope.

Personally, I do like the convenience that I can bind several variables with one `let` keyword in a case expression. And I can't see the appeal to making `~=`, let alone a completely new special-syntax assignment operator, more prominent in Swift.

— Pyry

···

On 28 Feb 2017, at 21.01, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

(…)


(Anton Zhilin) #7

Hi Erica, thanks for restarting the discussion—I hope that it will be
considered on topic for stage 2.

I agree that the part with preventing let .case(existingVar, newVar) should
be moved out of the proposal, because these issues are orthogonal. So the
options with ~= and := will differ only in that the former overloads an
existing custom operator.

I prefer the version with keywords instead of operators, because
overloading any custom operator can be confusing.

I think that pattern should be on the right, because that’s the order of
computation: first the expression, then pattern matching. That’s also how
switch looks: first expression, then patterns.

// That's what current if-case looks like. Counterintuitive!do {
    case .success(let value): …
    case .failure(let value): …
} switch computeResult(foo(frobnicate), bar)

Examples with matches and is, for extra brevity:

if computeResult(foo(frobnicate), bar) matches .success(let value) { … }
if computeResult(foo(frobnicate), bar) is .success(let value) { … }

I also considered using ->. It would never be ambiguous in this context,
but keywords still look better IMO:

if computeResult(foo(frobnicate), bar) -> .success(let value) { … }


(Xiaodi Wu) #8

Agree. Design 1 seems like an improvement over the status quo. Barring
unexpected downsides to this, it's then a question of whether the
improvements are large enough to justify a breaking change.

···

On Tue, Feb 28, 2017 at 3:57 PM, David Hart via swift-evolution < swift-evolution@swift.org> wrote:

I’m happy that someone is trying to fix this small wart in the language.
I’ve always wanted something like Design 1. It makes sense because we are
already used to the pattern matching operator and we finally fix the
inconsistencies between if/guard and switch.

On 28 Feb 2017, at 20:01, Erica Sadun via swift-evolution < > swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance
(eliminating edge case errors by adopting at-site conditional binding) and
one of style (using the pattern match operator consistently). Its
discussion was deferred from Phase 1 and remains in a fairly early stage.
Your feedback will help me decide whether this is a proposal I want to keep
developing or one that I should set aside and focus on other matters. Thank
you. -- E

The work-in-progress gist is here: https://gist.github.com/erica
/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

   - Proposal: TBD
   - Author: Erica Sadun <https://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>
Introduction

This proposal re-architects case syntax grammar to reduce potential errors
and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>
Motivation

In its current design, Swift case binding suffers from two weaknesses.

   - Mixed external and internal let/var binding may introduce errors
   from uncommon edge cases.
   - Real-world users may not consider the parallel construction between if
   case/guard case with switchstatements or naturally connect the two
   layouts.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal
Case Binding

When pattern matching, it's common to bind a variable or constant. It's
uncommon but legal to use a bound value as an argument. Adopting an "always
explicit, always within the parentheses" rule adds consistency and safety
to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated valuesenum Value<T> { case one(T), two(T, T), three(T, T, T) }
// An example with two associated valueslet example2: Value<Character> = .two("a", "b")
// A bound symbollet oldValue = "x"

This code's goal is to conditionally bind newValue and pattern match the
value stored in the oldValue symbol. The first example succeeds. The
second example compiles and runs but does not match the coder's intent.
Using an external letcreates a new oldValue shadow instead of pattern
matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

In-parenthesis binding avoids accidental shadowing. It eliminates this
class of error by adding let and var key words to each use point. This
creates longer call sites but enumerations rarely contain more than three
or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if
case let and if case var (plus case varand case let) may look like single
compound keywords rather than a combination of two distinct actions to
developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern
Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement,
moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}
if case .enumeration(let embedded) = value

The status quo for the = operator is iteratively built up in this fashion:

   - = performs assignment
   - let x = performs binding
   - if let x = performs conditional binding on optionals
   - if case .foo(let x) = performs conditional binding on enumerations
   *and* applies pattern matching

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:

if range ~= myValue { ... } // simplerif case range = myValue { ... } // confusing

Issues with the current design include:

   - guard case and if case look like standard non-conditional assignment
   statements but they are *not* assignment statements. Using the
   assignment operator violates the principle of least astonishment
   <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
   - In switch, a case is followed by a colon, not an assignment operator.
   - Swift *has* a pattern matching operator (~=) but does not use it
   here.
   - case syntax is wordy. The statement includes case, =, and optionally
   let/var conditional binding. Design alternatives could streamline this
   syntax, enhance clarity, and introduce a more concise format.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed
Design

This proposal adopts point-of-use conditional binding and recommends one
of the following designs. A successful design will replace the current
syntax with a simpler grammar that prioritizes pattern matching and support
conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design
1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results
look like this, showcasing a variety of letplacement, variable binding,
and optional sugar alternatives.

guard .success(let value) ~= result else { ... }guard .success(var value) ~= result else { ... }if .success(let value) ~= result { ... }if .success(var value) ~= result { ... }guard let x? ~= anOptional else { ... }if let x? ~= anOptional { ... }

In this design:

   - The case keyword is subsumed into the (existing) pattern matching
   operator
   - The statements adopt the existing if-let/if var and guard-let/guard
   var syntax, including Optionalsyntactic sugar.

if let x = anOptional { ... } // currentif case let x? = anOptional { ... } // would be removedif let x? ~= anOptional { ... } // proposed replacement for `if case`

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:

if range ~= myValue { ... } // beforeif case range = myValue { ... } // beforeif range ~= myValue { ... } // after

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design
2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This
operator eliminates the need for explicit let, although the keyword is
allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... } guard .success(let value) := result else { ... }if .success(value) := result { ... }if .success(let value) := result { ... }guard value? := anOptional else { ... } // newly legal, although unnecessaryguard let value? := anOptional else { ... } // newly legal, although unnecessary

Assignments to variables require the var keyword, enabling coders to
clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied letguard .pair(let value1, var value2) := result else { ... } // explicit letif .success(var value) := result { ... } // variable assignmentguard var x? := anOptional else { ... } // variable assignmentguard var x := anOptional else { ... } // simpler variable assignmentguard var x = anOptional else { ... } // even simpler (current) variable assignmentguard x := anOptional else { ... } // new constant assignment

Adopting this syntax provides more natural results for binding associated
enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded
from this proposal

This proposal does not address switch case or for case beyond internal
binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact
on Existing Code

This proposal is breaking and would require migration. External let or var would
automatically be moved by fixits into use points. Current guard case and if
case syntax would be migrated to the new design.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>
Timeline

Although removing if case and guard case are breaking, this proposal
should wait until Swift 4 Stage two to allow proper debate and
consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives
Considered

   - Leaving the grammar as-is, albeit confusing
   - Retaining case and replacing the equal sign with ~= (pattern
   matching) or : (to match the switch statement).
   - Adding matches or is as an alternative to the pattern matching
   operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Goffredo Marocchi) #9

I would think that removing confusion and simplifying + rationalising the syntax IS the definition of a breaking change justified :). I do not see how Erica's proposal is in any way contrary to the philosophy behind the language or is removing anything people really depend on now (in terms of expressive ability).

This should be nowhere near as controversial as the removal of pre and post increments and C style for loops, quite a slam dunk actually.

···

Sent from my iPhone

On 28 Feb 2017, at 23:45, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Agree. Design 1 seems like an improvement over the status quo. Barring unexpected downsides to this, it's then a question of whether the improvements are large enough to justify a breaking change.

On Tue, Feb 28, 2017 at 3:57 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:
I’m happy that someone is trying to fix this small wart in the language. I’ve always wanted something like Design 1. It makes sense because we are already used to the pattern matching operator and we finally fix the inconsistencies between if/guard and switch.

On 28 Feb 2017, at 20:01, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax
Proposal: TBD
Author: Erica Sadun
Status: TBD
Review manager: TBD
Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case

Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pyry Jahkola) #10

I guess I should also include the example where the user actually wanted the oldValue to be "x":

    if case let .two(newValue, value) = example, value == oldValue { ... }

No surprises there, even if another conditional is required.

— Pyry

···

On 1 Mar 2017, at 22.53, Pyry Jahkola via swift-evolution <swift-evolution@swift.org> wrote:

Erica,

Instead of going into all these lengths introducing new syntax, why not simply turn it into a warning when a `case let` binding shadows an existing variable with the exact same type (which supposedly also conforms to Equatable)?

Examples, including how to silence the said warning:

    enum Value<T> { case one(T), two(T, T), three(T, T, T) }
    
    let example: Value<String> = .two("a", "b")
    let oldValue = "x"
    // (Besides, you probably intended `oldValue` to be a `Character` in your
    // example. Well, it's a `String` in mine.)
    
    if case let .two(newValue, oldValue) = example { ... }
    // ~~~~~~~~
    // warning: 'oldValue' shadows an existing variable of same type 'String'
    
    if case let .two(newValue, (oldValue)) = example { assert(oldValue == "b") }
    // Ok, adding extra parentheses silences the warning.

    if case let .one(example) = example { ... }
    // Ok, because there's no way the author would equate the `example: String`
    // in the LHS to the `example: Value<String>` of the RHS.
    
    let maybe: Optional = "perhaps"
    if case let maybe? = maybe { ... }
    if case let .some(maybe) = maybe { ... }
    // Again, none of these examples would be affected by the warning, because
    // the `maybe: String` bound in the `case let` has a different type than
    // the `maybe: String?` in the outer scope.

Personally, I do like the convenience that I can bind several variables with one `let` keyword in a case expression. And I can't see the appeal to making `~=`, let alone a completely new special-syntax assignment operator, more prominent in Swift.

— Pyry

On 28 Feb 2017, at 21.01, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

(…)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Pyry Jahkola
pyry.jahkola@iki.fi


(Xiaodi Wu) #11

The criteria for a source-breaking change in Swift 4 are as follows:

- The existing syntax/API being changed must be actively harmful.
- The new syntax/API must clearly be better and not conflict with existing
Swift syntax.
- There must be a reasonably automatable migration path for existing code.

···

On Wed, Mar 1, 2017 at 02:30 Goffredo Marocchi <panajev@gmail.com> wrote:

I would think that removing confusion and simplifying + rationalising the
syntax IS the definition of a breaking change justified :). I do not see
how Erica's proposal is in any way contrary to the philosophy behind the
language or is removing anything people really depend on now (in terms of
expressive ability).

This should be nowhere near as controversial as the removal of pre and
post increments and C style for loops, quite a slam dunk actually.

Sent from my iPhone

On 28 Feb 2017, at 23:45, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

Agree. Design 1 seems like an improvement over the status quo. Barring
unexpected downsides to this, it's then a question of whether the
improvements are large enough to justify a breaking change.

On Tue, Feb 28, 2017 at 3:57 PM, David Hart via swift-evolution < > swift-evolution@swift.org> wrote:

I’m happy that someone is trying to fix this small wart in the language.
I’ve always wanted something like Design 1. It makes sense because we are
already used to the pattern matching operator and we finally fix the
inconsistencies between if/guard and switch.

On 28 Feb 2017, at 20:01, Erica Sadun via swift-evolution < > swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance
(eliminating edge case errors by adopting at-site conditional binding) and
one of style (using the pattern match operator consistently). Its
discussion was deferred from Phase 1 and remains in a fairly early stage.
Your feedback will help me decide whether this is a proposal I want to keep
developing or one that I should set aside and focus on other matters. Thank
you. -- E

The work-in-progress gist is here:
https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

   - Proposal: TBD
   - Author: Erica Sadun <https://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>
Introduction

This proposal re-architects case syntax grammar to reduce potential errors
and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>
Motivation

In its current design, Swift case binding suffers from two weaknesses.

   - Mixed external and internal let/var binding may introduce errors
   from uncommon edge cases.
   - Real-world users may not consider the parallel construction between if
   case/guard case with switchstatements or naturally connect the two
   layouts.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal
Case Binding

When pattern matching, it's common to bind a variable or constant. It's
uncommon but legal to use a bound value as an argument. Adopting an "always
explicit, always within the parentheses" rule adds consistency and safety
to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated valuesenum Value<T> { case one(T), two(T, T), three(T, T, T) }
// An example with two associated valueslet example2: Value<Character> = .two("a", "b")
// A bound symbollet oldValue = "x"

This code's goal is to conditionally bind newValue and pattern match the
value stored in the oldValue symbol. The first example succeeds. The
second example compiles and runs but does not match the coder's intent.
Using an external letcreates a new oldValue shadow instead of pattern
matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

In-parenthesis binding avoids accidental shadowing. It eliminates this
class of error by adding let and var key words to each use point. This
creates longer call sites but enumerations rarely contain more than three
or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if
case let and if case var (plus case varand case let) may look like single
compound keywords rather than a combination of two distinct actions to
developers unfamiliar with this syntax.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#pattern-matching-with-conditional-binding>Pattern
Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement,
moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}
if case .enumeration(let embedded) = value

The status quo for the = operator is iteratively built up in this fashion:

   - = performs assignment
   - let x = performs binding
   - if let x = performs conditional binding on optionals
   - if case .foo(let x) = performs conditional binding on enumerations
   *and* applies pattern matching

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:

if range ~= myValue { ... } // simplerif case range = myValue { ... } // confusing

Issues with the current design include:

   - guard case and if case look like standard non-conditional assignment
   statements but they are *not* assignment statements. Using the
   assignment operator violates the principle of least astonishment
   <https://en.wikipedia.org/wiki/Principle_of_least_astonishment>.
   - In switch, a case is followed by a colon, not an assignment operator.
   - Swift *has* a pattern matching operator (~=) but does not use it
   here.
   - case syntax is wordy. The statement includes case, =, and optionally
   let/var conditional binding. Design alternatives could streamline this
   syntax, enhance clarity, and introduce a more concise format.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#detailed-design>Detailed
Design

This proposal adopts point-of-use conditional binding and recommends one
of the following designs. A successful design will replace the current
syntax with a simpler grammar that prioritizes pattern matching and support
conditional binding.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-1-using-the-pattern-matching-operator>Design
1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results
look like this, showcasing a variety of letplacement, variable binding,
and optional sugar alternatives.

guard .success(let value) ~= result else { ... }guard .success(var value) ~= result else { ... }if .success(let value) ~= result { ... }if .success(var value) ~= result { ... }guard let x? ~= anOptional else { ... }if let x? ~= anOptional { ... }

In this design:

   - The case keyword is subsumed into the (existing) pattern matching
   operator
   - The statements adopt the existing if-let/if var and guard-let/guard
   var syntax, including Optionalsyntactic sugar.

if let x = anOptional { ... } // currentif case let x? = anOptional { ... } // would be removedif let x? ~= anOptional { ... } // proposed replacement for `if case`

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:

if range ~= myValue { ... } // beforeif case range = myValue { ... } // beforeif range ~= myValue { ... } // after

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#design-2-using-a-declare-and-assign-operator>Design
2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This
operator eliminates the need for explicit let, although the keyword is
allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... } guard .success(let value) := result else { ... }if .success(value) := result { ... }if .success(let value) := result { ... }guard value? := anOptional else { ... } // newly legal, although unnecessaryguard let value? := anOptional else { ... } // newly legal, although unnecessary

Assignments to variables require the var keyword, enabling coders to
clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied letguard .pair(let value1, var value2) := result else { ... } // explicit letif .success(var value) := result { ... } // variable assignmentguard var x? := anOptional else { ... } // variable assignmentguard var x := anOptional else { ... } // simpler variable assignmentguard var x = anOptional else { ... } // even simpler (current) variable assignmentguard x := anOptional else { ... } // new constant assignment

Adopting this syntax provides more natural results for binding associated
enumeration variables.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#excluded-from-this-proposal>Excluded
from this proposal

This proposal does not address switch case or for case beyond internal
binding requirements.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#impact-on-existing-code>Impact
on Existing Code

This proposal is breaking and would require migration. External let or var would
automatically be moved by fixits into use points. Current guard case and if
case syntax would be migrated to the new design.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#timeline>
Timeline

Although removing if case and guard case are breaking, this proposal
should wait until Swift 4 Stage two to allow proper debate and
consideration from the core team.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#alternatives-considered>Alternatives
Considered

   - Leaving the grammar as-is, albeit confusing
   - Retaining case and replacing the equal sign with ~= (pattern
   matching) or : (to match the switch statement).
   - Adding matches or is as an alternative to the pattern matching
   operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #12

Bug report filed with warning emitting request:

https://bugs.swift.org/browse/SR-4174

-- E

···

On Mar 1, 2017, at 1:58 PM, Pyry Jahkola <pyry.jahkola@iki.fi> wrote:

I guess I should also include the example where the user actually wanted the oldValue to be "x":

    if case let .two(newValue, value) = example, value == oldValue { ... }

No surprises there, even if another conditional is required.

— Pyry

On 1 Mar 2017, at 22.53, Pyry Jahkola via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Erica,

Instead of going into all these lengths introducing new syntax, why not simply turn it into a warning when a `case let` binding shadows an existing variable with the exact same type (which supposedly also conforms to Equatable)?

Examples, including how to silence the said warning:

    enum Value<T> { case one(T), two(T, T), three(T, T, T) }
    
    let example: Value<String> = .two("a", "b")
    let oldValue = "x"
    // (Besides, you probably intended `oldValue` to be a `Character` in your
    // example. Well, it's a `String` in mine.)
    
    if case let .two(newValue, oldValue) = example { ... }
    // ~~~~~~~~
    // warning: 'oldValue' shadows an existing variable of same type 'String'
    
    if case let .two(newValue, (oldValue)) = example { assert(oldValue == "b") }
    // Ok, adding extra parentheses silences the warning.

    if case let .one(example) = example { ... }
    // Ok, because there's no way the author would equate the `example: String`
    // in the LHS to the `example: Value<String>` of the RHS.
    
    let maybe: Optional = "perhaps"
    if case let maybe? = maybe { ... }
    if case let .some(maybe) = maybe { ... }
    // Again, none of these examples would be affected by the warning, because
    // the `maybe: String` bound in the `case let` has a different type than
    // the `maybe: String?` in the outer scope.

Personally, I do like the convenience that I can bind several variables with one `let` keyword in a case expression. And I can't see the appeal to making `~=`, let alone a completely new special-syntax assignment operator, more prominent in Swift.

— Pyry

On 28 Feb 2017, at 21.01, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

Proposal: TBD
Author: Erica Sadun <https://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

(…)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Pyry Jahkola
pyry.jahkola@iki.fi <mailto:pyry.jahkola@iki.fi>


(David Hart) #13

Would you agree with the following arguments?

The criteria for a source-breaking change in Swift 4 are as follows:

- The existing syntax/API being changed must be actively harmful.

The current pattern matching syntax is inconsistent which makes it hard even for experts to get correct (I often have to check it up) and is confusing for newcomers because of the use of the assignment operator. Both of these arguments tend to show that the existing syntax is actively harmful.

- The new syntax/API must clearly be better and not conflict with existing Swift syntax.

Design 1:
• fixes the inconsistencies in optional pattern binding between switch and if/guard
• reduces the number of different positions the let keyword can be in for enum matching, improving its learnability
• uses an existing operator which clarifies the pattern matching intent for newcomers

- There must be a reasonably automatable migration path for existing code.

All these changes affect syntax on expressions and can be migrated like by line without looking at the environnement of the piece of code, simplifying its migration.

···

On 1 Mar 2017, at 15:57, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Mar 1, 2017 at 02:30 Goffredo Marocchi <panajev@gmail.com> wrote:
I would think that removing confusion and simplifying + rationalising the syntax IS the definition of a breaking change justified :). I do not see how Erica's proposal is in any way contrary to the philosophy behind the language or is removing anything people really depend on now (in terms of expressive ability).

This should be nowhere near as controversial as the removal of pre and post increments and C style for loops, quite a slam dunk actually.

Sent from my iPhone

On 28 Feb 2017, at 23:45, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Agree. Design 1 seems like an improvement over the status quo. Barring unexpected downsides to this, it's then a question of whether the improvements are large enough to justify a breaking change.

On Tue, Feb 28, 2017 at 3:57 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:
I’m happy that someone is trying to fix this small wart in the language. I’ve always wanted something like Design 1. It makes sense because we are already used to the pattern matching operator and we finally fix the inconsistencies between if/guard and switch.

On 28 Feb 2017, at 20:01, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance (eliminating edge case errors by adopting at-site conditional binding) and one of style (using the pattern match operator consistently). Its discussion was deferred from Phase 1 and remains in a fairly early stage. Your feedback will help me decide whether this is a proposal I want to keep developing or one that I should set aside and focus on other matters. Thank you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax
Proposal: TBD
Author: Erica Sadun
Status: TBD
Review manager: TBD
Introduction

This proposal re-architects case syntax grammar to reduce potential errors and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case

Motivation

In its current design, Swift case binding suffers from two weaknesses.

Mixed external and internal let/var binding may introduce errors from uncommon edge cases.
Real-world users may not consider the parallel construction between if case/guard case with switchstatements or naturally connect the two layouts.
Internal Case Binding

When pattern matching, it's common to bind a variable or constant. It's uncommon but legal to use a bound value as an argument. Adopting an "always explicit, always within the parentheses" rule adds consistency and safety to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated values
enum Value<T> { case one(T), two(T, T), three(T, T, T) }

// An example with two associated values
let example2: Value<Character> = .two("a", "b")

// A bound symbol
let oldValue = "x"
This code's goal is to conditionally bind newValue and pattern match the value stored in the oldValue symbol. The first example succeeds. The second example compiles and runs but does not match the coder's intent. Using an external letcreates a new oldValue shadow instead of pattern matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}
In-parenthesis binding avoids accidental shadowing. It eliminates this class of error by adding let and var key words to each use point. This creates longer call sites but enumerations rarely contain more than three or four associated items.

Adopting point-of-use binding enhances clarity and readability. Both if case let and if case var (plus case varand case let) may look like single compound keywords rather than a combination of two distinct actions to developers unfamiliar with this syntax.

Pattern Matching with Conditional Binding

Swift's guard case and if case align statement design with the switch statement, moving the matched value to the right of an equal sign.

switch value {
    case .enumeration(let embedded): ...
}

if case .enumeration(let embedded) = value
The status quo for the = operator is iteratively built up in this fashion:

= performs assignment
let x = performs binding
if let x = performs conditional binding on optionals
if case .foo(let x) = performs conditional binding on enumerations and applies pattern matching
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:

if range ~= myValue { ... } // simpler
if case range = myValue { ... } // confusing
Issues with the current design include:

guard case and if case look like standard non-conditional assignment statements but they are not assignment statements. Using the assignment operator violates the principle of least astonishment.
In switch, a case is followed by a colon, not an assignment operator.
Swift has a pattern matching operator (~=) but does not use it here.
case syntax is wordy. The statement includes case, =, and optionally let/var conditional binding. Design alternatives could streamline this syntax, enhance clarity, and introduce a more concise format.
Detailed Design

This proposal adopts point-of-use conditional binding and recommends one of the following designs. A successful design will replace the current syntax with a simpler grammar that prioritizes pattern matching and support conditional binding.

Design 1: Using the Pattern Matching Operator

This design drops the case keyword and replaces = with ~=. The results look like this, showcasing a variety of letplacement, variable binding, and optional sugar alternatives.

guard .success(let value) ~= result else { ... }
guard .success(var value) ~= result else { ... }
if .success(let value) ~= result { ... }
if .success(var value) ~= result { ... }
guard let x? ~= anOptional else { ... }
if let x? ~= anOptional { ... }
In this design:

The case keyword is subsumed into the (existing) pattern matching operator
The statements adopt the existing if-let/if var and guard-let/guard var syntax, including Optionalsyntactic sugar.
if let x = anOptional { ... } // current

if case let x? = anOptional { ... } // would be removed
if let x? ~= anOptional { ... } // proposed replacement for `if case`
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:

if range ~= myValue { ... } // before
if case range = myValue { ... } // before

if range ~= myValue { ... } // after
Design 2: Using a Declare and Assign Operator

This design introduces new := "declare and assign" operator. This operator eliminates the need for explicit let, although the keyword is allowed and most house style guides would recommend its use:

guard .success(value) := result else { ... }
guard .success(let value) := result else { ... }
if .success(value) := result { ... }
if .success(let value) := result { ... }
guard value? := anOptional else { ... } // newly legal, although unnecessary
guard let value? := anOptional else { ... } // newly legal, although unnecessary
Assignments to variables require the var keyword, enabling coders to clarify the distinct roles in mix-and-match pattern matching:

guard .pair(value1, var value2) := result else { ... } // implied let
guard .pair(let value1, var value2) := result else { ... } // explicit let
if .success(var value) := result { ... } // variable assignment
guard var x? := anOptional else { ... } // variable assignment
guard var x := anOptional else { ... } // simpler variable assignment
guard var x = anOptional else { ... } // even simpler (current) variable assignment
guard x := anOptional else { ... } // new constant assignment
Adopting this syntax provides more natural results for binding associated enumeration variables.

Excluded from this proposal

This proposal does not address switch case or for case beyond internal binding requirements.

Impact on Existing Code

This proposal is breaking and would require migration. External let or var would automatically be moved by fixits into use points. Current guard case and if case syntax would be migrated to the new design.

Timeline

Although removing if case and guard case are breaking, this proposal should wait until Swift 4 Stage two to allow proper debate and consideration from the core team.

Alternatives Considered

Leaving the grammar as-is, albeit confusing
Retaining case and replacing the equal sign with ~= (pattern matching) or : (to match the switch statement).
Adding matches or is as an alternative to the pattern matching operator

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(TJ Usiyan) #14

I support the first part, removing `case let` in a switch. I actually
prefer one let out front in many ways but I have a much stronger preference
for eliminating a 'style' choice that dramatically impacts the
interpretation of the code. Binding each value is more explicit, so I am
fine with it winning.

The `if case` stuff… I don't really agree with as much.

TJ

···

On Mon, Mar 6, 2017 at 7:35 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

Bug report filed with warning emitting request:

https://bugs.swift.org/browse/SR-4174

-- E

On Mar 1, 2017, at 1:58 PM, Pyry Jahkola <pyry.jahkola@iki.fi> wrote:

I guess I should also include the example where the user actually wanted
the oldValue to be "x":

    *if case let* .two(newValue, value) = example, value == oldValue {
... }

No surprises there, even if another conditional is required.

— Pyry

On 1 Mar 2017, at 22.53, Pyry Jahkola via swift-evolution < > swift-evolution@swift.org> wrote:

Erica,

Instead of going into all these lengths introducing new syntax, why not
simply turn it into a warning when a `*case let*` binding shadows an
existing variable with the exact same type (which supposedly also conforms
to Equatable)?

Examples, including how to silence the said warning:

    *enum* Value<T> { *case* one(T), two(T, T), three(T, T, T) }

    *let* example: Value<String> = .two("a", "b")
    *let* oldValue = "x"
    // (Besides, you probably intended `oldValue` to be a `Character` in
your
    // example. Well, it's a `String` in mine.)

    *if case let* .two(newValue, oldValue) = example { ... }
    // ~~~~~~~~
* // warning: 'oldValue' shadows an existing variable of same type
'String'*

    *if case let* .two(newValue, (oldValue)) = example { assert(oldValue
== "b") }
    // Ok, adding extra parentheses silences the warning.

    *if case let* .one(example) = example { ... }
    // Ok, because there's no way the author would equate the `example:
String`
    // in the LHS to the `example: Value<String>` of the RHS.

    *let* maybe: Optional = "perhaps"
    *if case let* maybe? = maybe { ... }
    *if case let* .some(maybe) = maybe { ... }
    // Again, none of these examples would be affected by the warning,
because
    // the `maybe: String` bound in the `case let` has a different type
than
    // the `maybe: String?` in the outer scope.

Personally, I do like the convenience that I can bind several variables
with one `*let*` keyword in a case expression. And I can't see the appeal
to making `~=`, let alone a completely new special-syntax assignment
operator, more prominent in Swift.

— Pyry

On 28 Feb 2017, at 21.01, Erica Sadun via swift-evolution < > swift-evolution@swift.org> wrote:

The following draft proposal addresses one matter of substance
(eliminating edge case errors by adopting at-site conditional binding) and
one of style (using the pattern match operator consistently). Its
discussion was deferred from Phase 1 and remains in a fairly early stage.
Your feedback will help me decide whether this is a proposal I want to keep
developing or one that I should set aside and focus on other matters. Thank
you. -- E

The work-in-progress gist is here: https://gist.github.com/erica/
06dad9bbe1a70290fe6b89a64f73bc0c

Simplifying case syntax

   - Proposal: TBD
   - Author: Erica Sadun <https://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#introduction>
Introduction

This proposal re-architects case syntax grammar to reduce potential errors
and simplify unwrapping enumerations.

Swift-evolution thread: [Pitch] Reimagining guard case/if case
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161024/tbd.html>
<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#motivation>
Motivation

In its current design, Swift case binding suffers from two weaknesses.

   - Mixed external and internal let/var binding may introduce errors
   from uncommon edge cases.
   - Real-world users may not consider the parallel construction between if
   case/guard case with switchstatements or naturally connect the two
   layouts.

<https://gist.github.com/erica/06dad9bbe1a70290fe6b89a64f73bc0c#internal-case-binding>Internal
Case Binding

When pattern matching, it's common to bind a variable or constant. It's
uncommon but legal to use a bound value as an argument. Adopting an "always
explicit, always within the parentheses" rule adds consistency and safety
to Swift.

Consider the following enumeration and values:

// An enum with one, two, or three associated valuesenum Value<T> { case one(T), two(T, T), three(T, T, T) }
// An example with two associated valueslet example2: Value<Character> = .two("a", "b")
// A bound symbollet oldValue = "x"

This code's goal is to conditionally bind newValue and pattern match the
value stored in the oldValue symbol. The first example succeeds. The
second example compiles and runs but does not match the coder's intent.
Using an external letcreates a new oldValue shadow instead of pattern
matching oldValue's stored value.

// Safe
if case .two(let newValue, oldValue) = example2 {
    ...
}

// Syntactically legal but incorrect
if case let .two(newValue, oldValue) = example2 {
    ...
}

(…)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Pyry Jahkola
pyry.jahkola@iki.fi

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #15

I've put together everything I have about cases and unwrapping here:

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/033588.html

So please move the discussion there. Thanks! -- E

I support the first part, removing `case let` in a switch. I actually prefer one let out front in many ways but I have a much stronger preference for eliminating a 'style' choice that dramatically impacts the interpretation of the code. Binding each value is more explicit, so I am fine with it winning.

I've broken it down to its own problem/solution

The `if case` stuff… I don't really agree with as much.

I grant it's one of the things that would have gone down much better way earlier in Swift's development timeline, but that kept getting pushed forward again and again.

···

On Mar 6, 2017, at 8:47 PM, T.J. Usiyan <griotspeak@gmail.com> wrote:

TJ

On Mon, Mar 6, 2017 at 7:35 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Bug report filed with warning emitting request:

https://bugs.swift.org/browse/SR-4174

-- E

On Mar 1, 2017, at 1:58 PM, Pyry Jahkola <pyry.jahkola@iki.fi <mailto:pyry.jahkola@iki.fi>> wrote:

I guess I should also include the example where the user actually wanted the oldValue to be "x":

    if case let .two(newValue, value) = example, value == oldValue { ... }

No surprises there, even if another conditional is required.

— Pyry


(David Waite) #16

I don’t fully understand - are you saying to comment on the gist as if it were a discussion forum?

-DW

···

On Mar 7, 2017, at 1:25 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

I've put together everything I have about cases and unwrapping here:

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/033588.html

So please move the discussion there. Thanks! -- E

On Mar 6, 2017, at 8:47 PM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

I support the first part, removing `case let` in a switch. I actually prefer one let out front in many ways but I have a much stronger preference for eliminating a 'style' choice that dramatically impacts the interpretation of the code. Binding each value is more explicit, so I am fine with it winning.

I've broken it down to its own problem/solution

The `if case` stuff… I don't really agree with as much.

I grant it's one of the things that would have gone down much better way earlier in Swift's development timeline, but that kept getting pushed forward again and again.

TJ

On Mon, Mar 6, 2017 at 7:35 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Bug report filed with warning emitting request:

https://bugs.swift.org/browse/SR-4174

-- E

On Mar 1, 2017, at 1:58 PM, Pyry Jahkola <pyry.jahkola@iki.fi <mailto:pyry.jahkola@iki.fi>> wrote:

I guess I should also include the example where the user actually wanted the oldValue to be "x":

    if case let .two(newValue, value) = example, value == oldValue { ... }

No surprises there, even if another conditional is required.

— Pyry

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Erica Sadun) #17

Please respond to that email thread rather than this one. Thank you.

-- E

···

On Mar 7, 2017, at 7:25 PM, David Waite <david@alkaline-solutions.com> wrote:

I don’t fully understand - are you saying to comment on the gist as if it were a discussion forum?

-DW

On Mar 7, 2017, at 1:25 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I've put together everything I have about cases and unwrapping here:

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/033588.html

So please move the discussion there. Thanks! -- E

On Mar 6, 2017, at 8:47 PM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

I support the first part, removing `case let` in a switch. I actually prefer one let out front in many ways but I have a much stronger preference for eliminating a 'style' choice that dramatically impacts the interpretation of the code. Binding each value is more explicit, so I am fine with it winning.

I've broken it down to its own problem/solution

The `if case` stuff… I don't really agree with as much.

I grant it's one of the things that would have gone down much better way earlier in Swift's development timeline, but that kept getting pushed forward again and again.

TJ

On Mon, Mar 6, 2017 at 7:35 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Bug report filed with warning emitting request:

https://bugs.swift.org/browse/SR-4174

-- E

On Mar 1, 2017, at 1:58 PM, Pyry Jahkola <pyry.jahkola@iki.fi <mailto:pyry.jahkola@iki.fi>> wrote:

I guess I should also include the example where the user actually wanted the oldValue to be "x":

    if case let .two(newValue, value) = example, value == oldValue { ... }

No surprises there, even if another conditional is required.

— Pyry

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution