[swift-evolution] [Draft] Tuple-Based Compound Optional Binding

Xiaodi Wu xiaodi.wu at gmail.com
Sun Jun 12 13:04:54 CDT 2016

On Sun, Jun 12, 2016 at 6:46 AM, Brent Royal-Gordon via swift-evolution <
swift-evolution at swift.org> wrote:

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

I think this proposal is definitely worthy of consideration (and
implementation). I hesitate that what works or not is being proposed partly
on the basis of whether it's difficult to implement or not. If I could
offer a suggestion as to exactly which edge cases should work, I think it
should be based on this guideline that can be articulated cleanly:

Given `if let (a, b, c)...` or any other variation or edge case, transform
in your mind's eye to `if case let (a?, b?, c?)...`. That is, append `case`
before `let` and then append `?` immediately after each constant so
introduced. If the `if case let...` version would work, so should the `if
let...` version. By that rule of thumb, `if case let (a?, (b?, c?)) =
(opt1, (opt2, opt3))` would work if I'm not mistaken, as would `if case let
(a?, b?) = tuple`, but `if case let tuple? = (opt1, opt2)` would not and
thus neither should `if let tuple = (opt1, opt2)`.

> <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 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/20160612/5fd2a033/attachment.html>

More information about the swift-evolution mailing list