[swift-evolution] [Discussion] Simplifying case syntax

Jaden Geller jaden.geller at gmail.com
Tue Feb 28 14:45:49 CST 2017


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 at 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 <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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170228/d3b7bba3/attachment-0001.html>


More information about the swift-evolution mailing list