SE-0231 — Optional iteration

Ah, but are there any that are also sequences that you'd expect to run into them as optional? There are a lot of transforming sequences or programmatically infinite sequences, etc, but the only time you are likely to have an optional sequence (rather than an empty one) is when it is stored or perhaps encoded. And these kinds of sequences, I submit, are easy to initialize empty.

3 Likes

It may be less common, especially when working with concrete types but I'm sure there would be some cases that come up. It is also possible to have generic code constrained only to Sequence, in which case there is no way to construct a value at all.

As I said, I haven't read the final proposal yet so I am not taking a position. But I do think these are use cases that support an argument for syntactic sugar of some kind. Whether they are common enough to provide enough to warrant accepting the proposal is a separate question of course. :slight_smile:

1 Like

What is your evaluation of the proposal?

+1, but noting that for e in sequence? {} better fits

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

Enumerating Optional collections is clearly a problem many people face. This feature is purely additive, which if implemented as unwrapping the value, introduces no confusion, and would serve to eliminate nesting or unnecessary (and still expensive) coalescing.

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

for e in elements? {} is a good solution and more clear than for?, since it is specifically elements that is Optional and being enumerated.

I disagree with the following:

for element in sequence? { ... }
Since a nonterminated optional chain is otherwise meaningless, this can be interpreted as bringing the for loop into the optional chain with the sequence

I think this can only happen if one does not understand the usage of ?, {, }, and for-in as seen in the rest of Swift.

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

n/a

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

Read the proposal and have had to work around enumerating Optional collections previously.

4 Likes

I'll just point out that optional chaining will easily give you an optional sequence even if the sequence itself (as stored in the object) is not optional. All it takes is one optional in the chain:

for item in sortCriteriaPopup?.menu?.items ?? [] { ... }

for xmlElement in root.first("body")?.children ?? [] { ... }

for segment in previousTable?.segments ?? [] { ... }
9 Likes
  • What is your evaluation of the proposal?

I was (and remain) opposed to the implicit variant initially pitched. I am modestly in support of an explicit version using either for? or in?. in? feels a bit more closely associated with the iterated value which seems like appropriate. On the other hand, for? makes it immediately clear at the beginning of the statement that the iteration is conditional which is also beneficial.

I do not think the sequenceExpression? syntax that has been discussed is a good choice. It does not benefit from moving the conditionality forward in the statement and is too similar to optional chaining.

I trust the core team to choose the best syntax if the proposal is accepted.

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

Mildly. There isn't much value in common cases beyond ?? []. However, in some cases I think there would be increased clarity in moving the conditionality of the iteration earlier in the statement. Also, as I mentioned in a previous reply there are use cases where it is not possible to use ??. In those cases, clarity would be added by avoiding the if let and associated indentation.

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

Yes. As mentioned in the proposal, optional sugar is prevalent in Swift. The proposed sugar feels very similar to existing sugar.

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

I have not used any other languages that deeply embed special handling of and syntactic sugar for optional values.

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

I participated in discussion threads and gave the final proposal a quick glance.

Right, and CollectionOfOne cannot have an empty instance constructed.

• • •

I expect the two most-common situations in which people want to iterate an optional sequence are when the sequence is obtained through optional chaining, or by subscripting a dictionary.

Regardless of where the optional came from, I don’t see any benefit to requiring an extra question mark. It seems perfectly Swifty for both of these to work:

for x in dictionary[key] { … }

for x in value.optionalProperty?.sequence { … }
1 Like

The more I think about it, the more I think the for construct should participate in the optional chaining. For instance, if I were to get an iterator for the two above, I'd write it like this:

let it1 = dictionary[key]?.makeIterator() 

let it2 = value.optionalProperty?.sequence.makeIterator()

One needs a ? in front of makeIterator(), while the later does not. If you were to translate this to a for loop, the syntax would then work like this:

for x in dictionary[key]? { … }

for x in value.optionalProperty?.sequence { … }

So, only the former would need a ?. And sequence itself isn't optional, so no ? for the later.

What is your evaluation of the proposal?

+1, using the for? spelling.

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

Absolutely. I've encountered this many times in my own code. Embedding the loop inside an if let block adds a level of indentation, which can hurt readability in some cases when the loop itself contains several levels of indentation.

I'd like to respond to some of criticisms left by other reviewers:

  1. Use ?? []
    Certainly there are some cases where this is possible, but it isn't generalisable to all custom sequences, for which there is no sensible 'empty sequence'. Array literal conversion is a nice feature, which may make the creation of some sequences more readable, but it should not be required for such a basic operation as iteration just because you have an optional value somewhere.

  2. Don't use optional collections
    As others have mentioned, you can easily end up with an optional sequence through chaining. There are also huge semantic differences between empty sequences and nil values, which can be very important in some programs. There are occasions where somebody would still like to iterate the sequence (if it exists). I don't consider this an adequate alternative at all.

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

I think it does, especially with the for? spelling. That said, I was quite surprised that the example given in the "motivation" section compiles, as AFAIK switch does not skip optional values or handle them transparently:

enum AB {
  case a
  case b
}

let val: AB? = .a
switch val {
case .a: print() // Error: Enum case 'a' not found in type 'AB?'
case .b: print()
}

Even adding a default handler does not make the above work. You need to use .some(.a) to handle the nested enum case.

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

You can iterate nil collection instances in Obj-C. But Obj-C has pretty funky handling of nils anyway.

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

Read the proposal and the other reviews.

2 Likes

I am a +1 on this proposal (except for the ? location). It is fairly common to get a sequence from a dictionary or optional chaining, and a lightweight syntax for handling it that fits with the many other uses of ? would be appropriate.

You could make the same argument about optional chaining in general – why should you have to write a question mark? Send-to-nil doing nothing has its fans, but it's a style that is explicitly eschewed by Swift. Silent handling of nil should be acknowledged, via the ? sigil that has clear meaning wherever it appears. That is exactly what this proposal would allow, for iteration.

The analogy with switch is missing a key difference: you must still explicitly handle all cases (even if that explicit handing is sticking in a default: clause). You cannot just write this, for example:

let b: Bool? = true
switch b { // Error: Switch must be exhaustive
case true: print("yay")
case false: print("boo")
}

An the analogy to silent optional iteration might work if the switch were just skipped over in the case of nil. This is not what happens with switch, and it should happen with iteration either.

Also, bear in mind the reason switch (1 as Int?????) { case 1: } works is because of Optional's conditional conformance to Equatable. Any Equatable value can be matched in a switch via the ~= that works on any equatable T. The equivalent for iteration would be to have Optional: Sequence where Wrapped: Sequence, with a flattening behavior for optionals of sequences. Which leads to more questions: should .some([1,2,3]).map(...) produce a non-optional array? Should filter?

It is definitely true that returning an optional array from a function is a common beginner mistake. But it's also sometimes the right choice. For example, when nil indicates failure, or "not specified" as a distinct value to specified as empty. Being able to confirm you want nil to mean the same as empty in your handling, via a simple bit of syntax consistent with other places where nils are acknowledged, but then then handled silently, seems in keeping with the rest of the language.

Placement of the ?

Placing the ? after the keyword is an inappropriate solution. ~I don't believe there are any other cases where ? is used with a keyword rather than a type or a variable.~ edit: doh, of course there are – e.g. try?.

The consistent placing should be on the sequence variable:

a!.doSomething()   // trap if a is nil
a?.doSomething()   // do nothing if a is nil
a![1] = 2  // trap if a is nil
a?[1] = 2  // do nothing if a is nil
a!.forEach { ... }  // trap if a is nil
a?.forEach { ... }  // do nothing if a is nil
for x in a! { ... }  // trap if a is nil
for x in a? { ... }  // do nothing if a is nil

The consistency here also gives me no qualms about explaining this to beginners.

18 Likes

as? and try?

Doh. Right.

…which to me would lend weight toward “in?”

Still not the same. try? produces an optional, which you must handle if you want the result, it doesn't silently ignore the throw like skipping the iteration would. In that respect, try? is more like Int? than x?.doSomething().

1 Like

Both as? and try? create optionals so I think they both support the idea that for? or in? to unwrap an optional is the wrong choice. The established way to unwrap an optional is to place the ? after the optional value itself.

24 Likes
  • What is your evaluation of the proposal?
    -1

  • Is the problem being addressed significant enough to warrant a change to Swift?
    No, don't see the driving need.

  • Does this proposal fit well with the feel and direction of Swift?
    if it must I'd prefer in? rather than for?
    and why not just optional?.forEach { ... }

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

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    Read the proposal and a few of the comments

From the documentation:

    /// Using the `forEach` method is distinct from a `for`-`in` loop in two
    /// important ways:
    ///
    /// 1. You cannot use a `break` or `continue` statement to exit the current
    ///    call of the `body` closure or skip subsequent calls.
    /// 2. Using the `return` statement in the `body` closure will exit only from
    ///    the current call to `body`, not from any outer scope, and won't skip
    ///    subsequent calls.
6 Likes

What is your evaluation of the proposal?

+1. I remember wanting this on the very first I spent experimenting with Swift.

Without strong feelings on the matter, or a clear rationale, I’m mildly in favor of the for … in? alternative. It reads better, somehow — especially when there’s a more complex pattern match to the left of the in:

let items: [Int?]? = ...

for case let x? in? items { ... }  // clearer that optionality of in? hinges on items

vs.

for? case let x? in items { ... }  // for? is visually distant from items;
                                   // what does question mark refer to?

I also see the case for putting the question mark after the expression, though somehow it doesn’t read as well in practice:

for child in? view?.subviews { ... }  // clearer that in? is testing the whole expr,
                                      // and that the whole loop might not run

for child in view?.subviews? { ... }  // it looks like the second ? is bound
                                      // to the subviews property in particular;
                                      // also not clear that its effect is on the for/in

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

Sure. It’s a small problem, but also a small solution.

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

In that aforementioned first Swift file, the feature felt conspicuous in its absence.

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

The other languages I know with similar optional sugar (Typescript, Ruby) use internal iteration (i.e. items.each or items.forEach). What about Kotlin and Ceylon? Do they have this feature?

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

Quick reading, plus years of casual wishing.

I often support convenience features. I like it when the code we write every day gets more and more fluid.

But we're talking about control flow, not about the standard library: the proposal changes the fabric of the language.

I'm questioning the underlying motivation, and I'm afraid the proposal is just trying to take Swift further apart from other languages, without much rationale.

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

No. The code below has no problem:

if let sequence = sequence {
    for element in sequence {
        ...
    }
}

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

Let's look at this table of control flow keywords, below:

keyword keyword? keyword!
try try? try!
while
if
for for?
switch
return

I guess switch? would turn missing after this proposal is implemented. Do we want it?

And return? looks actually quite handy:

var firstName: String?
var lastName: String?
func anyName() -> String? {
    return? firstName
    return? lastName
    return nil // could be omitted if return? were **super** handy
}

So. Are we planning something?

5 Likes

for x in xs? {} indeed is a more consistent, and obvious syntax. I'd go from -1 to -0.9 with this change :P

5 Likes

I agree.
Does it means that we should revise optionals-keywords interactions behaviour? Partially it already started in proposal review SE-0230: Flatten nested optionals resulting from `try?` - #71 by anandabits.