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

Johan Jensen jj at johanjensen.dk
Mon Jun 13 02:34:17 CDT 2016


Currently `if case let (foo?, bar?, baz?) = (_foo(), _bar(), _baz())`, and
_bar() returns nil, then `_baz()` will not be evaluated.

On Mon, Jun 13, 2016 at 2:54 AM, Xiaodi Wu via swift-evolution <
swift-evolution at swift.org> wrote:

> What's the behavior currently with `if case let (foo?, bar?, baz?)...`?
>
> On Sun, Jun 12, 2016 at 19:53 plx via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>> This proposal should specify if the tuple is evaluated “eagerly” or as an
>> “early-exit”.
>>
>> That is, if we write `if let (foo,bar,baz) = (_foo(), _bar(), _baz())`,
>> and _bar() -> nil, will `_baz()` have been evaluated, or not?
>>
>> I’d use this feature either way, but the proposal should have a clear
>> statement on this point.
>>
>> On 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.
>>
>> <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
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>
> _______________________________________________
> 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/20160613/21b8d069/attachment.html>


More information about the swift-evolution mailing list