On Sun, Jun 12, 2016 at 19:53 plx via swift-evolution < > swift-evolution@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@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@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