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:
binding.md · GitHub
Simplifying case syntax
- Proposal: TBD
- Author: Erica Sadun <https://github.com/erica>
- Status: TBD
- Review manager: TBD
<binding.md · GitHub;
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>
<binding.md · GitHub;
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.
<binding.md · GitHub
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.
<binding.md · GitHub
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.
<binding.md · GitHub
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.
<binding.md · GitHub
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
<binding.md · GitHub
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.
<binding.md · GitHub
from this proposal
This proposal does not address switch case or for case beyond internal
binding requirements.
<binding.md · GitHub
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.
<binding.md · GitHub;
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.
<binding.md · GitHub
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