When I suggested this syntax in the acceptance thread for SE-0099, Chris said 
it should be written up as a proposal. I'm sure this will get lost in the WWDC 
shuffle, but here goes.
Tuple-Based Compound Optional Binding
Proposal: TBD
Author: Brent Royal-Gordon <https://github.com/brentdax>
Status: TBD
Review manager: TBD
 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#introduction>Introduction

This proposal enhances optional binding with a new, tuple-based syntax for 
binding multiple values. It replaces functionality lost in SE-0099 with a 
syntax compatible with the new design.

Swift Evolution Discussion: [Accepted with Revision] SE-0099 Restructuring 
Condition Clauses 
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/19452/focus=20139>
 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#motivation>Motivation

In Swift 2, it was possible to bind multiple optional values in a single if 
let, guard let, or while let clause:

guard let a = opt1, b = opt2, c = opt3 else { ... }
SE-0099 
<https://github.com/apple/swift-evolution/blob/master/proposals/0099-conditionclauses.md>
 simplified the syntax of conditional statements, but removed this feature so 
that , could instead separate different conditional clauses. Code like this 
must now use three separate optional binding clauses:

guard let a = opt1, let b = opt2, let c = opt3 else { ... }
The similar case clause sidesteps this problem because it can pattern-match 
tuples. Hence, you can put several patterns in a tuple on the left side of the 
=, and a matching number of values in a tuple on the right side, and match them 
all with one case clause:

guard case (.none, .none, .none) = (opt1, opt2, opt3) else { ... }
This doesn't conflict with the clause separation syntax because the commas are 
within parentheses. However, the analogous syntax for optional bindings is not 
permitted:

guard let (a, b, c) = (opt1, opt2, opt3) else { ... }
// error: initializer for conditional binding must have 
// Optional type, not '(Int?, Int?, Int?)' (aka 
// '(Optional<Int>, Optional<Int>, Optional<Int>)')
 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#proposed-solution>Proposed
 Solution

We should extend optional binding clauses to permit a tuple of optional values 
on the right of the = and a tuple of constants with identical arity on the 
left. Swift should test each element of the tuple on the right, and if none of 
them are nil, bind them to the constants on the left.

Nothing in this proposal should change the way optional binding handles an 
optional tuple (T, U)?, as opposed to a tuple of optionals (T?, U?). Even an 
optional tuple of optionals (T?, U?)? should continue to be handled as before.

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#detailed-design>Detailed
 Design

No change to the formal grammar is necessary, as the pattern and initializer 
productions in the optional-binding-head rule can already match tuples:

optional-binding-head : 'let' pattern initializer
Rather, Sema should be modified to detect this situation and generate 
appropriate code. Currently, TypeCheckPattern.cpp essentially converts let a = 
opt1 into case let a? = opt1; if this proposal is accepted, it should similarly 
convert let (a, b) = (opt1, opt2) into case let (a?, b?) = (opt1, opt2).

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#edge-cases>Edge
 cases

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#nested-tuples-of-optionals>Nested
 tuples-of-optionals

Permitting deeper pattern matching of nested tuples is highly precedented by 
pattern matching, but is a niche feature. It should be supported if easily 
achievable.

guard let (a, (b, c)) = (opt1, (opt2, opt3)) else { ... }
 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#expressions-returning-tuples-of-optionals>Expressions
 returning tuples of optionals

Ideally, optional bindings whose initializer is an expression evaluating to a 
tuple of optionals would be supported:

let tuple = (opt1, opt2)
if let (a, b) = tuple { ... }
However, I'm not sure if Swift will have pinned down the type of the 
initializer at the point where it's generating the pattern. If this would be 
difficult or impossible to implement, Swift should continue to interpret code 
like this as attempting to bind an optional tuple, rather than a tuple of 
optionals.

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#single-name-patterns>Single-name
 patterns

In theory, Swift could allow you to bind a tuple of optionals to a single 
constant:

if let tuple = (opt1, opt2) { ... }
However, this seems error-prone; the pattern doesn't draw a very clear picture 
of the value being operated upon, so you could easily misinterpret it as 
binding an optional tuple. Because of this ambiguity, Swift should always 
interpret this construct as binding an optional tuple, rejecting it with a type 
error if necessary.

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#impact-on-existing-code>Impact
 on Existing Code

This proposal is additive compared to SE-0099, but in combination with it, 
essentially replaces the Swift 2.2 compound binding syntax with a different, 
incompatible one. When moving directly from Swift 2.2, the migrator should 
convert old-style compound optional binding clauses:

guard let a = opt1, b = opt2, c = opt3 else { ... }
Into the new, tuple-based ones:

guard let (a, b, c) = (opt1, opt2, opt3) else { ... }
The "one-let-per-binding" syntax remains compatible with both Swift 2.2 and 
Swift 3, so projects which must support both can still perform multiple 
optional bindings in a single if statement without resorting to an #if 
swift(>=3.0) build configuration:

guard let a = opt1, let b = opt2, let c = opt3 else { ... }
 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#alternatives-considered>Alternatives
 Considered

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#not-accepting-this-proposal>Not
 accepting this proposal

This proposal does not add new functionality; it merely removes keyword 
clutter. However, it offers a convenient replacement for a commonly-used 
feature which has just been removed as a result of grammatical ambiguity, not 
user confusion or lack of utility.

 
<https://gist.github.com/brentdax/46c340c967358589ade5351531ac8920#providing-similar-standard-library-functionality>Providing
 similar standard library functionality

Rather than including this functionality in the compiler, the standard library 
could provide a series of functions like:

public func all<T, U>(_ t: T?, _ u: U?) -> (T, U)? { ... }
public func all<T, U, V>(_ t: T?, _ u: U?, _ v: V?) -> (T, U, V)? { ... }
// etc.
These could then be used in a similar fashion to this proposal:

guard let (a, b, c) = all(opt1, opt2, opt3) else { ... }
However, because we do not have variadic generics, we would need to provide a 
set of overloads for different arities, and our support would be limited to the 
arities we chose to provide. (Support for tuples, as opposed to separate 
parameters, would require a second set of overloads). Meanwhile, the tuple 
matching syntax is already precedented in case conditionals, so extending it 
seems pretty natural. Providing this in the compiler seems like the right 
solution.


-- 
Brent Royal-Gordon
Architechies

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

Reply via email to