Still or again interest in optional iteration?

There is an old proposal for using for-in loops with optional sequences which was rejected in the proposed form (using for?, also see the discussion and some more discussion) but with the remark that there could be good alternatives, e.g. using for x in optionalSequence? { ... }.

I personally think that this could be useful (think longer chains) and also somehow would streamline the use of optionals (where even myVar? = "Hello" is valid code).

So what do you think?

2 Likes

A revised proposal should address the points the core team outlined in the rejection post:

Yes, of course.

The question is: Is an additional ? wanted here? try? operationThatMightThrow() is already an optional, so should the for-in loop be usable for any optional sequence without an additional ?, or should an additional ? be mandatory to tell the compiler that we know what we are doing (note that SE-0230: Flatten nested optionals resulting from `try?` is already implemented)? If the first is considered dangerous, then maybe the whole thing is not a good idea... At least for x in try? operationThatMightThrow() { ... } seems more logical to me.

1 Like

I wouldn't want a general rule that you can use optional collections with for loops, but if the optionality is introduced or acknowledged in some way in the collection expression, that'd be good enough for me. So I would say to look through optionality introduced by either/both of try? or ?-chaining.

Yes (correcting myself to some degree): It should be clear from the expression that we know that we are dealing with an optional.

I would say that either try throwingOptionalSequence()? or try? throwingOptionalSequence()? should work, but not just try? throwingOptionalSequence(), which has not acknowledged the optionality of the underlying collection result.

3 Likes

I would already assume several outcomes of this topic (OK, maybe a little too early to say, but let's see):

  • Optional iteration (in the alternative form) is something that people may want.
  • If the outer ? should always be set is an open question, one might want to allow to let it fall away if it is clear that we know we are dealing with an optional sequence.
  • for-in loops for any optional sequence (without making clear that we know we are dealing with an optional sequence) is probably not something we want.
1 Like

This currently available alternative is doing similar thing with a slight semantic change:

    items?.forEach { item in
        ...
        // beware of slight semantic change here, e.g. no break or return
    }

and this alternative seems to be ok:

    for item in items ?? [] {
        ...
    }
2 Likes

I would not like to change “current idioms” in my code, and having a for-in loop for the general case of an optional sequence is something you rather would not want, see above.

The goal is to be able to choose between the two without having to use empty sequences as alternative in one case. —

I have the impression that a new proposal using the for x in mySequence? { … } syntax has a good chance to be successful.

At this point I also think that it would be the most straight forward thing to allow for … in <expression> { … } if and if only <expression>.forEach { … } is a valid.

There might be arguments to allow e.g. for x in try? throwingSequence() { … }, see the discussion and also the according notes in the rejection of the old proposal, but then why not allow (try? throwingSequence()).forEach { … }, so maybe this then is a separate (and more controversial) topic.

Update: Being able to switch between the two different styles of for loops without having to change the <expression> (see above) could be a sensible goal in itself.

2 Likes

Personally, I'm against this.

What is the difference between .none and .some([])?

If there is no difference, I would argue that they should be merged together and sequence should be non-optional. And I would like this to happen as soon as possible in the processing pipeline.

If there is a difference, then .none should be handled explicitly, and with for? it may too easy to forget to handle it.

If it is sometimes one, sometimes another, I want cases where .none is converted to empty sequence to be explicit and carrying a comment.

4 Likes

I guess you mean the solution by @onnerb above, that you can always iterate over an optional sequence. @John_McCall (see his first comment) thinks differently. Hard to find a “logical” solution here. My “proposal” in my last comment is what I think a solution with less controversy so to speak.

Not exactly. I was arguing that the problem is artificial.

If you have a function f() that returns [Int]?, but everywhere you use it, you do f() ?? [], then the ?? [] part probably should be inside, and the function should just return non-optional [Int].

2 Likes

The interesting case is when you use it as "f() ?? []" in some places and as just "f()" in at least one other place. Distinction between "an empty egg box" and "no egg box" could be useful at times.

2 Likes

I process a lot of XML where e.g. you might want to iterate through the child elements of the first child of a section:

mySection.children.first?.children.forEach { block in … }

But I cannot write:

for block in mySection.children.first?.children { … }

Note: There also is another problem with the second code: The Swift compiler could not infer the type of block.

1 Like

Please note that what I personally would like to have goes further (see my last comment), it is about the interchangeability of the for-in loop with forEach without changing the expression, so the programmer can concentrate on which kind of loop she would like to have (a closure at the end of a chain, or the ability to break from the iteration). This is a kind of user-centric approach, but I think a valid one. I suppose for that you need to adjust the compilation of for-in loops, adding an operator will not suffice.

(…And just as a cross link for the mentioned problem of for-in loops “loosing” type info: see this comment in another topic.)

1 Like

I believe that's a bug... And we should probably not base our decision on whether to make a new feature or not taking that bug into account... (Imagine we do, and make the new feature, and then the corresponding bug is fixed – we may find ourselves in a situation that we don't need that feature anymore, and as it was shown on numerous occasions it's very hard to remove stuff from Swift once it's baked in so we may end up with having a feature we don't need).

Am I right assuming that the mentioned alternatives are "perceived" bad because:

  • a separate "if" check will introduce extra two lines of code plus extra level of indentation
  • "forEach" workaround is bad because there's a change of semantic compared to "for in" and we may want to prefer "for in" for some other reason (e.g. to have an ability to break or return).
  • "for x in elements ?? []" is bad because ... (please help me with this one why is it bad).

This approach of making optional a sequence is quite interesting and worth considering.

Nobody said so. The point was just that the cited code currently would have a problem even with the feature implemented. So on the contrary.

That would be

if let firstChild = mySection.children.first {
  for block in firstChild.children { … }
}

instead of

for block in mySection.children.first?.children { … }

and this maybe many times or even nested (real code might even be more complicated).

Well, the point is, why not be able to choose between the two without any hiccup in one case?

Your example introduces another array and in other cases other empty sequences when all what you want (as in the case of forEach) is that just nothing happens, and it is perfectly clear from the code in my examples that we know we are dealing with an optional (just as in the case of forEach). It looks like some artificial trick to overcome some limitation of your language.

The question here is, do we want to able to use an optional sequence in a for-in loop even if nothing in the for-in expression expresses this fact? You can see from the comments that this is at least contentious. And then, when changing your code to forEach, you need to add a ? in some cases. I like the symmetry expressed by my idea better and I think this idea should also be less controversial.

2 Likes

"expression" doesn't include trailing question mark symbol, for example these are syntax error:

var a: Int? = nil
var b: Int = 0
let x: Int? = a? // 🛑
let y: Int? = b? // 🛑

I believe you are exaggerating the need for the symmetry between "forEach" and "for in" and making it a goal. In practice the two ways of looping are quite different so one can't easily jump back and forth between the two (the obvious differences are "break" / "return" / "throw" treatment.

I do not understand your point, a trailing question mark would be necessary in exactly those cases where it would be needed for method calls. You could then also argument against optional chaining.

Update: By “expression” I did not mean any expression, but exactly what could be written before the dot of the method call. Please read it this way.

You might exactly want to jump to one of them because of those differences, e.g. to be able to stop the iteration. —

I still do not see any point why this symmetry should not be a good thing, it is convenient and “does not hurt”: old code is still valid, and you will never think you are using a non-optional sequence when you are actually using an optional one. And the “optional iteration” in the narrower sense is part of it in a natural way,

Let me elaborate this: Currently, you will be “penalized” if you choose the “wrong option”, see my examples above, even when you “need” this option e.g. if you would like to be able to stop the iteration.