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 
<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

Reply via email to