SE-0231 — Optional iteration

I don't think so. If the "rightmost end" would have the type of the entire optional chain expression, then you could never access the properties of anything but Optional, never access and dig further into properties (of peroperties, etc).

Optional chaining hides "outer" optional layer(s) that will always be there while we are still able to dig into the properties of properties, but the sugar hides that nesting, which is convenient but also obscures our thinking about these things.

I believe this is what @Chris_Lattner3 was suggesting, with for e in optionalSequence? { left as a degenerate form.

I really like the idea of thinking of for x in foo?.bar as encompassing the loop within the optional-chain, with for x in foo? simply being a degenerate version of that.

13 Likes

If I correctly understood the idea, Chris suggests to use ? only when there isn't a optional chain. So we're talking about further conditionalising the syntax. In my opinion this is what would complicate things, and it's more inconsistent. I feel like it's best to either always put that ? or keep things implicit. But I don't think Chris proposed this without reason. I'm probably missing something important as well.

1 Like

Take this:

optionalSequence?.forEach { ... }
optionalSequence?.reversed().forEach { ... }

Then remove the ".forEach" part:

optionalSequence? { ... }
optionalSequence?.reversed() { ... }

Then put this in a for loop:

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

There's no special rule.

5 Likes

But in what other contexts will users then expect myOptional? to work?

Adding ? is starting an optional chain. Normally you can't do that at the end of a chain because it's pointless: it doesn't change the type or semantics of the expression. But in this case it wouldn't be, so it'd be allowed.

2 Likes

I don't know that we would adopt it in any other contexts. It works nicely for for because skipping the loop coincides with the obvious semantics for iterating over a missing collection, which people naturally gloss as being essentially like an empty collection. That property doesn't obviously extend to other situations.

Another way of looking at it is that, if you just ignore/overlook the ? completely, you aren't really going to be surprised by the behavior.

1 Like

I'm thinking that if users note that the following error:

let a: [Int]? = [1, 2, 3]
for e in a { print(e) } // ERROR: Value of optional type '[Int]?' must be unwrapped to a value of type '[Int]'

can be silenced by:

for e in a? { print(e) }

and by its more "drastic" version:

for e in a! { print(e) }

then they might expect that the following error:

let a: String? = "a"
let b: String = "b"
let c = a + b // ERROR: Value of optional type 'String?' must be unwrapped to a value of type 'String'

can be silenced by:

let c = a? + b

especially since the following will compile:

let c = a! + b

And, perhaps a bit less far fetched / debatable:

They might expect that the following error:

var a: [Int] = [1, 2, 3]
let b: [Int]? = [4, 5, 6]
a.append(contentsOf: b) // ERROR: Value of optional type '[Int]?' must be unwrapped to a value of type '[Int]' [Note that the actual error here is not this, because of bad diagnostics]

can be silenced by:

a.append(contentsOf: b?)

especially since the following will compile:

a.append(contentsOf: b!)
3 Likes

Users try both of those things today. A lot. And I would like to address those problems eventually if we can find a nice way of doing it.

7 Likes

if I was new to Swift and this worked:

let b: [Int]? = [4, 5, 6]
for e in b! { print(e) } // OK
for e in b? { print(e) } // OK

and I'd learned that for-in iterates over sequences, then I would be very confused by eg the following:

var a: [Int] = [1, 2, 3]
let b: [Int]? = [4, 5, 6]
print(a + b!) // OK (Prints [1, 2, 3, 4, 5, 6])
print(a + b?) // ERROR 1: Binary operator '+' cannot be applied to operands of type '[Int]' and '[Int]?'
              // ERROR 2: Optional chain has no effect, expression already produces '[Int]?'

It would seem like a compiler bug, since b? must be an Int sequence in both examples, or a disturbing special case that only applies to for-in for some reason (that contributes to making the language complex and hard to understand).

So unless the proposal is to make both of the above examples (and related ones, for optional sequences) work, I'd be opposed.

1 Like

I understand your point, but again, people have exactly that reaction already: they learn about optional chaining, and then they run into one of its limitations, and then they get annoyed at the arbitrariness of the limitations. Not having this bit of sugar is not going to prevent anyone from running into those limitations eventually some other way even if (and this seems unlikely to me) they run into them first with for loops.

If we do find a way to lift those limitations it will undoubtedly include allowing x? in some more contexts.

1 Like

Yeah, that's a great intuitive explanation for this. +1 for that!

This isn't further conditionalizing the syntax. If you already have an optional chain, then you ready have a ?:

for x in optionalSequence?.reversed() { ... }
2 Likes

I get your point too, and I will try to be quiet after this post : )


Are there any current limitations or inconsistencies that are as blatant as the following?

This would compile and work as expected:

let a: [Int]? = [1, 2, 3]
for e in a? { print(e) }

while this would result in a compile time error:

let a: [Int]? = [1, 2, 3]
let b = a? // ERROR: Some error that tries to describe the concept of degenerate optional chaining and its mystical relation to for-in loop?
for e in b { print(e) }

(My probably totally naive and irrelevant feeling is that the energy required to implement this as a magic special case (expr can only occur after for-in) would be better spent on cleaning up the implementation / concepts so that the obvious way of implementing this would naturally work for all relevant cases.)

2 Likes

-1

No. from what i gather it seems to make iterating over an optional sequence slightly easier (than what currently exists) if all of the following are true:

  • You find yourself with an optional sequence. Like some people said optional chaining can cause this
  • You want to continue the function (so guard is out)
  • The sequence doesn't conform to ExpressibleByArrayLiteral (so ?? [] is out)
  • You also want to use control statements like continue and break or return from the function inside of your loop

The situation in which all of these are true doesn't seem to be very common, and I have yet to see any data that suggest otherwise.

Whether or not it can be rationalized as a "degenerate optional chain expression", it is definitely a new syntax that doesn't clearly express its functionality by reading the code. This is something people will see in the codebase and have to look up and wonder where else this same syntax can be used (which is nowhere).

Swift uses "progressive disclosure" to ease programmers into using less common syntax, but this isn't really relevant to programmers who are being introduced into a codebase in Swift that already uses this syntax or new users that find code on stackoverflow using it. It's important we don't think of additional syntactic sugar as "free". It has a mental cost on new users, and the functionality of the syntactic sugar should be worth more than that cost.

Personally, I don't think it is worth the cost. If Swift was expanding this syntax to be used in many more contexts like john said was a possible future, I may feel differently.

N/A

Read the proposal and discussion

3 Likes

We've seen repeated requests for things like x?.count + 1, yes.

I don't think there's a way to "clean up the concepts" here that could possibly lead to something like let x = y? meaning anything that wouldn't introduce its own much more serious complexity and compatibility problems.

1 Like

Imho there is a solution that would make things simpler instead of introducing any inconsistency - and I'm quite surprised about how many people complain about added complexity in this thread:
My memory may fail me, but I don't remember many who voiced this concern when huge new concepts like dynamic member lookup and all those automatic conformances (Codable...) have been introduced.

I really think Swift has too many small and specialized solutions and that it would be better to invest time and energy in powerful and composable features - but I see the inconsistency in that you can write

for x in seq! {}

but not

for x in seq? {}

Afair there has been at least one thread about "optional chaining for parameters" (yes, I seem to be right ;-): [Idea] Use optionals for non-optional parameters, Optional Argument Chaining) and people really want such shortcuts - it's just not possible to add them with a semantic as clear as in the case we are talking about here.

2 Likes

I, too, am surprised how some people expect untold, unplanned, unclear, unprepared big changes in the mental model of optionals, and in the language itself to be able to use this narrow-scoped proposal as a foot-in-the-door.

What started as a humble convenience-oriented proposal has unexpectedly turned into many expressions of frustrations about optionals (for loops, optional chaining, now x?.count + 1, etc).

If some people have concerns with optionals, have gathered pet peeves as time has passed, have untold long-term plans that were on hold, it's time to speak out loud and clear, for the benefit of the whole community.

I'm now waiting for some kind of "Optional Fluidity Manifesto" that would provide a high-level view of future expected changes in the language and the standard library, and make sure that they do improve the language, and that they are internally consistent.

We can also trust a benevolent guru, of course.

5 Likes

How is this surprising? The proposed solution (or the one that the review process seems to have deemed the most probable) is the following:

let x: [Int]? = nil
for e in x? { ... } 

And if that (which to my eyes looks like it has everything to do with for loops and optional chaining) should work, then I'd say that it's just a simple logical consequence that x?.count + 1 == 0 + 1 == 1, anything else would be asking the user to try to internalize and maintain a very complex mental model of how the language works.

I agree that Swift SE has introduced too many special cases to Swift already. But the problem can thus not be that some people have been silent in some reviews while voicing their concerns about inconsistencies of other proposals, the problem must be that too few reviewers are concerned about consistency / composability / simplicity.

2 Likes

So maybe it's time to take a little break from this thread, and take a rest. Because some other reviewers are really concerned. And it happens that some of them support this proposal - or at least support a rethinking of some aspects of optionals, in which this proposal is a piece of the puzzle.

Reading their opinion is interesting. Even if I'm reluctant accepting this proposal as it is, even if I'm not buying some arguments that are, as you say, not concerned enough (or, to be nice, written in hurry), I'm still vey interested with alternative opinions, and I want to foster an environment where they can flourish and, maybe, convince. I love to be surprised :-)