SE-0231 — Optional iteration

Hm, right. I can see how that would make things ambiguous. In my mind, this reinforces how we shouldn't make this a compiler feature. Look how making optional chaining a compiler feature instead a postfix-? and a proxy-like thing has gotten us stuck now ;)

So yeah, I'm definitely on the side of "we should not do this".

1 Like

Couldn't the postfix ? be retrofitted to be used in optional chaining? (I mean it's unlikely that this would be changed so late in the game but still?)

I'm +1 on the for x in xs? {} syntax

This is a natural extension of other uses of ? in the language, and is a great parallel to for x in xs! {}

3 Likes

I think this was also said upthread, but this back-and-forth made it click for me! The makeIterator() call is already implicit in a for statment. Allowing the trailing ? is just this: an optional chain to that implicit call. This seems even more fitting and consistent to me now than thinking about it as a pattern match.

(I'd suggest this be added to the proposal, except that the trailing ? is only an alternative.)

After lengthy consideration, I believe it makes sense to say that a postfix question-mark means, essentially, “unwrap or do nothing”. Even if we can’t (or don’t) formally define it as such in the language, it makes an easy-to-reason-about mental model.

Thus optional chaining “acts like” the question-mark first unwraps the value, then the dot accesses a member of it—unless the original value is nil in which case it does nothing and the result is nil.

And the current proposal becomes “for x in sequence?” where again the question-mark “acts like” it unwraps the sequence if possible, and otherwise skips the loop entirely.

Applying the same principle to optional Bool naturally produces the behavior @Mojo described above, where you can write “if anOptionalBool? { … }” .

I think this will simplify and improve the language in real-world code both for experienced Swift developers and for people who are new to the language.

5 Likes
  • What is your evaluation of the proposal?

+1 to the alternative location of the ? after the optional value.

  • Is the problem being addressed significant enough to warrant a change to Swift?

I frequently find myself reflexively writing for let s = seq, x in s { ... }, but I never care about the binding of s beyond the rest of the for loop. I prefer the proposed semantics of nil meaning no iteration.

  • Does this proposal fit well with the feel and direction of Swift?

The alternative ? after the optional value feels consistent with optional chaining.

It might be inconsistent with optional patterns as the "Alternatives Considered" section mentions. Then again, I always have to look up the syntax for optional patterns every time I read or write them, so I'd prefer consistency with chaining over consistency with optional patterns.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Not with this degree of built-in syntactic support for optional and preference for procedural code.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Brief reading of proposal and thread.

1 Like

I know you updated some of the text around it, and thanks for your work here, but I think the switch example should just be removed from the proposal entirely because it has caused a lot of confusion in this review thread. Conceptually there's no particular convenience feature for optionals in switch statements, just the standard implicit conversion of non-optionals to optionals available throughout Swift. Unless you're proposing a similar implicit conversion of optional sequences to empty sequences (e.g. conditional conformance of Optional to Sequence) this section is just misleading.

If accepted, I think that the for item in items? syntax makes the most sense, because I find the arguments about the parallels with for item in items! and case x? in switch statements convincing.

+½, but only for the for element in sequence? syntax.

It's a relatively minor use case, and I wouldn't feel bad if it got rejected, but I really like the idea of optional-chaining from within a statement, and I hope this paves the way towards things like foo(42, bar?) as sugar for bar.map { foo(42, $0) } in future. I never really understood (in practice, not implementation) why you could chain on the callee, but not the arguments.

Definitely. Optional chaining is some of the most expressive syntactic sugar Swift (currently) offers, and I'm all for extending it to further use cases. Reducing indentation levels is always welcome, too.

A quick read through most of the proposal, as well as much of this thread.

To reiterate the points I found most important and which led me to this decision:

  • The other keywords currently suffix-able with ? (and !, for that matter) are as? and try?. In both cases, these keywords don't (necessarily) operate on optionals, but instead return them. The proposed for? would operate on optionals, which is an important distinction to make.
  • Silently handling the optional feels wrong, because everywhere else, optionals are handled explicitly, with ? (silent failure/conditional execution) or ! (fatal failure).
  • The for element in sequence? results in beautiful consistency with other places, as Ben noted:
6 Likes

I'm am pretty strongly -1 on this proposal.

No. This is strictly a syntactic sugar proposal, and the Swift 5 process states that the bar for such is:

Syntactic additions . Syntactic changes do not increase the expressive power of the language but do increase its complexity. Consequently, such changes must be extremely well-motivated and will be subject to additional scrutiny. We will expect proposals to include concrete data about how widespread the positive impact will be.

This proposal does not include concrete data about how widespread the positive impact will be, and the motivation is particularly weak. In particular:

Coalescing with ?? [] is only valid with types that conform to ExpressibleByArrayLiteral .

This is technically correct, but all interesting types do conform (or conform to ExpressibleByDictionaryLiteral). The cases that are not covered by this proposal are not enumerated. If this were a widespread problem, then it should be easy to list those types as concrete motivators.

The argument against the forEach method isn't an argument relevant to this discussion, it is an argument against the forEach method itself.

No. Optional processing is admittedly not perfectly uniform in Swift (see the contemporary try? proposal) but we've tried very hard to keep the sigils aligned in Swift so that ? forms of things have corresponding ! versions of the same operators, and this dillutes the meaning of the sigil. This includes postfix versions of the operators, try?, as?, etc. A for! statement doesn't make sense.

Similarly, there is a consistency issue with the rest of the control flow statements. Are we going to introduce if? and while? operators to sugar optional processing in those as well?

IMO, this proposal would make the language more complicated to sugar ?? [] into a single ?. Even in the fully general case, it eliminates one if let and a level of indentation. This does not (to me) reach the level of something we should consider sugaring, and there is no data shown to indicate that this would have a widespread impact on Swift programmers.

n/a

I didn't follow the pitch thread or discussion, but I did read the proposal in detail, and I also read the patch. The patch has numerous technical problems, including 4 or 5 sets of logging left in the patch like this:

    OEE->print(llvm::outs());llvm::outs()<<"\n\n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n\n";
//    auto result = rewriter.visitOptionalEvaluationExpr(oee);
    
//    if (!OEE)
//      return nullptr;
//    rewriter.finalize(oee);
    return OEE;
  }

as well as several other technical issues.

Suggestion:

In an attempt to be helpful, I'll observe that the majority of the use cases outlined in the review thread here are relating to optional chaining. At the same time, we have no plans to make optional itself be a Sequence, so a for-in over something that produces optional will never "mean anything" in a for-in sequence position.

Instead of solving this problem with a for? statement, I think we could say either:

  1. for-in implicitly looks through up to one level of optional (I dislike this approach), or:
  2. An optional-chain in a for-in statement is implicitly ignored when it produces nil.

Note that the second provides a "sweet deal" where the majority of the use cases above "just work", but where a for-in over some other optional sequence can be handled by adding one punctuation character:

  for x in someOptionalSequence? {

because that is a (degenerate) optional chain expression.

-Chris

14 Likes

Yeah, the discussion had largely settled on the for x in sequence? degenerate optional chain syntax. Although, I think you're the first to propose that this work for any optional chain expression.

As I tried to point out earlier, I think it'd make sense for all optional chains to be allowed, even those that do not terminate with a ?:

1 Like

Mmm, I’m now neutral on the proposal if it terminates with ‘?’ but do feel that it should end with such an operator: conceptually, in my mind at least, it’s as though optionally chaining with the for...in loop, analogous to ‘x?.forEach’.

If you don’t need to terminate with a ‘?’ then any optional value should work without ‘?’, but that’s been a roundly rejected approach for reasons we’ve discussed.

2 Likes

But you don't necessarily need a ? before the .forEach if forEach is part of the chain:

value.optionalProperty?.sequence.forEach { ... }

However, if you break the chain by adding a parenthesis, then you need to form a new optional chain with ?:

(value.optionalProperty?.sequence)?.forEach { ... }

I'd expect a for loop supporting optional chaining to work similarly.

1 Like

Can you elaborate?
I can't see any break in symmetry when we'd follow

I was looking for a crack in the pristine landscape drawn by @Ben_Cohen. Instead of using a, I tried to use a?.x as the base expression. In this case, the last line (the "new for") was the only one with a trailing question mark (and this was the crack I was looking for, the symmetry break):

for x in a?.x? { ... }  // do nothing if a?.x is nil

And eventually I wondered if this was a useful contribution, so I destroyed this message.

Ah, I see your point. Yes, that makes sense.

I'm -1 since the benefits are much smaller than the cost (added complexity to the language). I wish Swift's feature set was smaller, more consistent and more composable.


Perhaps the following might be worth thinking about:
if someOptionalSequence? is to be considered a (degenerate) optional chain expression, and they work like this in the context of sequences and for-in loops, then in what other contexts would users expect them to work?

3 Likes

Oh dear, just saw this one: Seems you really hate this proposal when you compare it with force unwrapping (instead using guard let).
It either supports my theory that nobody actually reads all those reviews, or that I don't know anything about how good Swift should look like ;-)
(or do the dangers of this pattern only exist in my imagination?)

First of all, that code would be better written as:

guard let sequence = sequence else {
    return
}

for element in sequence { }

Secondly, that's an insufficient solution in comparison to the proposed solution. It doesn't let you "carry on" to do stuff past the for loop, like:

alwaysCallMe()
for x in dontIterateMeIfImNil? {}
alwaysCallMe()
3 Likes

The proposal:

Coalescing with ?? [] is only valid with types that conform to ExpressibleByArrayLiteral .

davedelong:

return EmptyCollection() // yes I know this won't work; but you get the point

Could we add the following to Sequence and solve all this?

static var empty: Self { get }

Then you could do:

for x in (optionalSequence ?? .empty) {
...
}

I disagree with the proposal as written. Could maybe see in?. However, I don't think I'm alone in preferring forEach over the for-in construct.