SE-0231 — Optional iteration

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.

0.5. I really don't like the proposed syntax since it doesn't fit with Swift's current style. Adding ? to such a common keyword has the risk of making a simple for loop hard to understand for newcomers. I am also in favor of adding the optional mark to the in keyword which better shows the action that the loop performs. It gives more clarity when reading the syntax:

for i in? sequence { ... }

I would read this "for each i in the optional sequence", do something.

Yes.

Yes, but as I said the proposed syntax doesn't meet the current Swift cosmetic in the way I see it.

N/A

Read the proposal multiple times as well as all the posts in this discussion.

Interesting. The fixit seems coordinated and suggests the following, which does work. I have to admit this is the first time I see an actual use case for an "unterminated optional chain".

switch val {
case .a?: print("rrrrrr")
case .b?: print()
default: print()
}

However, this works as well, which is suspicious:

switch val {
case let x where x == .a: print()
case let x where x == .b: print()
default: print()
}

Could the reason the original statement fails be that the compiler fails to recognize the synthezised Equatable conformance when pattern-matching? @Ben_Cohen could you help us figure this out?

2 Likes

Wow, I never knew that syntax existed. I guess that does kind of lead me to favour the for x in val? syntax.

I suspect it's more that the type checker is for some reason reluctant to upgrade .a to an optional in order for the ~=<T: Equatable>(lhs: T, rhs: T) to kick in (for T of AB?). @rudkx might know why.

It does happen with variables (note it can't be exhaustive in that case):

let val: AB? = .a
let aye = AB.a
let bee = AB.b
switch val {
case aye: print() 
case bee: print()
default: print()
}

I wouldn't describe that syntax as an unterminated optional chain. It's more like .a? is shorthand for .some(.a). Hence this is exhaustive:

let val: AB? = .a
switch val {
case .a?: print("rrrrrr")
case .b?: print()
case nil: print()
}

in the same way this is:

enum AB { case a,b }
enum MyOptional<T> { case some(T), none }

let val: MyOptional<AB> = .some(.a)
switch val {
case .some(.a): print("a")
case .some(.b): print("b")
case .none: print("none")
}
3 Likes

I support it. This is something that would have come in handy from time to time in the past. I also prefer placing the ? after the sequence identifier for the reason that was mentioned earlier: consistency with which syntax produces vs. unwraps optionals.

As an additive change, I think so.

Yes.

I haven't.

Read the proposal and the thread.

What would be the downside if syntax did not change, and the for loop simply handled an optional iteratator as if it was empty. ie, nothing to iterate over???

I am against any “for?” Or “in?”. These are not easy for new comers, and I can’t think of why this can’t just work with existing for-in syntax.

Switch statements handles optionals without any special syntax.

The downside would be that developers might accidentally use this feature, not realizing that the sequence they’re iterating can be optional, and thereby introduce a bug.

This behavior should be explicit for the same reason optional chaining is explicit: to avoid accidentally using the feature without realizing you’re using it.

3 Likes

I see.

Does for? means someone will want a while?? I fee like the requests for the conviences of unwrapping would be endless if this is accepted. Nil coalescing operator is much clearer and more consistant IMO.

5 Likes

Yeah, I realized that, the "unterminated optional chain" was just my way of describing the ? :slightly_smiling_face:

I will definitely update the proposal to emphasize this - as already mentioned, switch is always exhaustive, hence you still have to handle the nil case. Optional iteration implies skipping optionals, which is different.

1 Like

To make a slippery slope argument you really need to show there's a slope to slip down.

There is already good handling in while for optionals via pattern matching:

var iterator = [1,2,3].makeIterator()
while let x = iterator.next() {
   print(x, separator: ",") // prints 1, 2, 3
}

And since optional chaining is flattening, you don't really get the same problem with while that you do with for:

var a: [Int]? = .some([1,2,3])
var iterator = a?.makeIterator()
while let x = iterator?.next() {
  print(x)
}

If while didn't already have these features, would it make for a suitable evolution proposal to add them? Definitely!

For the sake of argument, let's take a different case: adding together two optionals. This is actually pretty hard to figure out and a pain to write even if you know how. There are lots of ways to solve this: overloading +; adding a +?; adding the ability to lift any infix operator to take two optionals; adding a kind of free equivalent of Optional.map that takes two optionals. Rather than be worried that adding optional sugar for for leading to more proposals to tackle things like this, I think this is an area worth exploring since it's a source of pain for developers currently.

7 Likes

What is your evaluation of the proposal?

-1. I don't consider iteration over optional sequence to be so special to get its own sugar. Moreover, i think it will have a negative impact on the language (more below).

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

No and the proposal provides no proof or explanation that this is a significant improvement over the existing, so-called "workarounds".

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

No. In my opinion, this proposal will have negative impact on a couple of aspects:

1. Learning Swift

Right now, unwrapping optionals and iterating sequences are two distinct ideas in Swift and can be easily understood in isolation. Beginners learn how to do these things and then they can glue this knowledge together when facing an optional sequence.

Introduction of special control flow operator for this case will only make the language more complex and will raise further questions, like where's while?, switch?, if?.

2. API design

After this feature is introduced, thus making optional sequences a "blessed" type, I feel that API authors will be more inclined to misuse optional sequences where other types would make more sense.

For instance, I think usage of optional sequences will increase in return types and completion closures, where non-optional sequence wrapped in Result type, or throwing function would be a better choice.

3. Safety

This is my most important point, I'll do my best to explain what I mean. TL;DR: For me this syntax shares similar safety implications as the ability to send messages to nils in Objective-C.

Going through explicit unwrapping or coalescing has the advantage that... a developer must actually write that. And as they write it, they usually think about the difference between an empty and an optional sequence. In the end, they might decide to deal with the optionality in an earlier stage.

With the introduction of for?, the difference between iterating on a non-optional empty array and an optional array becomes blurred. So blurred, it might go unnoticed during development and code review and might have serious complications, where a different code path is required for these two cases.

Now, re. the proposed fix-it — I think fix-its should increase program safety and teach developers how to properly handle cases that caused them. In my opinion, this particular fix-it should draw developer's attention to the difference between optional sequences and non-optional empty sequences. It should not suggest omitting one of these cases silently. There's a reason for that when you write try, Swift doesn't suggest adding try? — instead, it suggests adding a proper do/catch.

To sum up, I feel this proposal will make it harder for developers to catch and distinguish the use of optional sequences, thus making it easier to introduce bugs in our products. Swift, with its opinionated focus on safety, should not go in that direction.

Not all developers are experienced enough that they won't fall into the aforementioned trap. Swift should make it harder for less experienced developers to make mistakes. And this proposal undermines that mission.

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

Nah, I don't think any other language has it.

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

I read the proposal, scanned the initial pitch thread and read discussion in this thread.

8 Likes

FWIW TSPL refers to this as an "optional pattern".

3 Likes