Grammatically, ? is just a postfix operator; the restriction against allowing it at the end of the chain is artificial, meant only to prevent a no-op syntax. In fact, there's already a situation we allow it at the end of a chain of postfix expressions: on the left-hand-side of an assignment, where it has similar "the RHS and assignment are part of the chain" semantics.
Ah, thanks, so what will the type of hmm be (question 3 in the details below)?
Or if it will be an error, what will the error message be?
And is there any counterpart to this currently in the language, ie a sequence type expression which cannot be factored out from
for in seqExpr { … }
to
let s = seqExpr
for in s { ... }
?
For completeness here is the (now partly answered) list of questions.
1. Would it involve messing with the grammar? Not really.
2. Is changing parsing rules likely to have unpleasant ramifications that we aren't expecting? N/A
3. What would the type of hmm be, or what would the compile time error be, here:
or perhaps even the whole for e in optSeq? { … }
?
That required some mental gymnastics to understand. I would not want to be the one writing the docs for this, or explaining the concepts for learners that are inclined to "really getting it".
As long as you have legitimate questions, asking them is fine. It did sometimes feel like your questions were just setting up opportunities to repeat that you find the model complicated because there are all these questions about it.
I asked them because I genuinely wanted to understand the solution and its implications. I do find the model complicated, otherwise I wouldn't have had to ask all these questions while trying to understand it. Maybe I'm the only one who finds it much too complicated.
My opposition would center around this. Swift seems to solve corner cases by adding more corner sugar syntax. This just makes the language more and more like C(++). It's sad to me really. Fix forEach (or rather, the weak closure story) so it can support arbitrary returns and now you've solved this problem generally. This particular case is then dealt with optional chaining that is a highly scalable pattern used throughout the language.
Specific details aside here, similar examples of hoisting parts of statements into seemingly-equivalent variables have come up in this review thread and other review threads. I just wanted to point out that there is no general Swift principle saying that this sort of thing should usually work (i.e. give equivalent results), and in fact it fails in even very simple examples because of inferred literal types, type inference generally, etc.
Right. And of direct pertinence here, it fails for every optional chain with more than one postfix component: you can extract let b = a?.foo out of a?.foo.bar(), but you still have to write b?.bar() after the extraction, not just b.bar().
OK, thanks for the replies. I'm really genuinely trying to understand. I think my mental model of a for-in statement, which is roughly: for A in B [where C] { … } (seems like the optional where clause is undocumented btw)
makes me see optSeq? as an expression that can stand in for B, and this makes me wonder what kind of expression optSeq? is and whether for-in loops will be able to iterate over other things than sequences, etc. I also found it really challenging to understand that the boundaries of the supposed degenerate optional chain could extended beyond B (which is optSeq?) to include parts of the for-in loop itself, which I now guess is something that also happens in eg optX? = 123.
But perhaps I should instead change my mental model to something like this: for A in B[?] [where C] { … }
and that the ? after B belongs to the keywords of the construct (forinwhere) and is not a postfix operator to B, and can be used for when B is an optional sequence expression. If I manage to keep everything in my head right now that way of looking at it means that there is no need for any concept of degenerate optional chain syntax.
(I still wish Swift had simpler and less overloaded syntax. I find it really hard to talk and think clearly about, but I guess that might be just me.)
The issue this proposal attempts to solve is one that I find is a common point of friction in the language, and I frequently run into this. Bringing optional chaining to iteration is an excellent idea, and one that I think will serve developers quite well.
Using for? makes sense in this context and I agree with its usage. Swift has similar syntax already (e.g., try?), which has a similar idea behind it.
The proposal serves the language very well, and is another feature that makes Swift handle optionals even better. It also brings consistency to the way optionals are handled throughout the entire language, which is important for both experienced and new users of Swift.
I quickly read this proposal, but immediately agreed with its premise, and the proposed solution.
Interesting! I didn't know this. To me it does seem like a bug more than a feature though:
1> var thing: Int? = 5
thing: Int? = 5
2> thing? = 10
$R0: ()? = nil // what is this??
3> thing
$R1: thing? = 10 // ok I'm kind of surprised this worked given the above
4> thing = 5 // no return value from this expression, as I think most devs would expect (assignment in swift is documented as such)
5>
You appear to be a proponent of the originally-proposed approach @John_McCall, so I'd be curious to hear your thoughts about just allowing for element in optional {} as outlined here SE-0231 — Optional iteration - #261 by Geordie_J and by a few other people throughout this thread. Sorry if you've already addressed this, I will admit I didn't read through all 275 responses.
Given what you're saying about the optional chaining syntax though, maybe for element in optional {} would be the functional equivalent as for element in optional? {} – both would rely on the idea of a no-op in the case of nil, and this case of for loops would no longer by syntactic sugar at all, but rather an extension of the (new) "base case" I just mentioned. That is something I could get behind, although my personal preference would be to deprecate all cases of optional chaining ending with the ? operator, because I don't see what value it's adding to readability or conveying developer intention (and in the cases it does work it causes - to me - unexpected behaviour).
Here's what I've collected. The compatibility suite only includes projects compatible with cocoapods.
Pattern
Regular Expression
?? literal
`for\s(.+)\s??\s+(
if let ... for
`if\s+(let
guard let ... for
`guard\s+(var
for-in
`for\s+(\w+
These regular expressions do not account for cases when statements are separated by other statements in a multiline manner.
Codebase
?? literal
if let ... for
guard let... for
for-in
Swift LOC
App1
3
0
0
31
5529
App2
0
2
0
71
14976
App3
2
22
1
88
18126
App4
0
6
0
57
18971
App5
0
12
0
175
27401
–
swift-corelibs-xctest
0
0
0
7
2298
swift-syntax
0
0
0
21
3142
swift-xcode-playground-support
0
0
0
33
7271
swift-nio
0
1
0
218
37143
swift-package-manager
2
2
0
264
40725
swift-corelibs-foundation
0
13
0
346
75529
Standard Library
0
0
0
276
82683
Source Compat Suite
5
34
6
600
164158
SDK overlays
0
4
0
–
–
I observe that a significant proportion of participants are unhappy with the degenerate optional chain solution (hey @Jens). While I understand the thinking behind the approach, I can also understand the people that point out inconsistency. Not that it isn't swifty, but I can see how the approach expects too much back from the language. It's like having a spaceship parked in your back yard. Plus, syntactically breaking apart a simple expression and an optional chain feels exotic.
That said, I would like to revive a bit the discussion on thinking about sequence? as a degenerate form of an optional pattern rather than an optional chain. Degenerate because we don't bind the unwrapped value to an identifier (at least explicitly), so all we can write is optional?. Here's an illustration:
for element in case _? = sequence { ... } // case _? = value is valid optional pattern syntax
// shortened to
for element in sequence? { ... } // where sequence? is a degenerate optional pattern
The same degenerate optional pattern was already mentioned in the hypothetical if against an optional:
if sequence? { ... }
// as a shorthand for
if case _? = sequence { ... }
This isn't easy for me to reason about either, and to some degree I prefer to trust more experienced people with a broader picture of the language and it's roadmap. For instance, I had no idea you could have ? at the end of a left-hand side optional expression.
It's a bug that the REPL is printing the "result" of the assignment here, and I don't know why it's printing nil of all things. The REPL is supposed to suppress its default logic of printing results in cases like this.
I didn't even know about the "case _? ="-thing so I tried it out and put it in .some context:
let optVal: Int? = 123
if case _? = optVal { print(" 1" ) }
if case let v? = optVal { print(" 3", v) }
if case let .some(v) = optVal { print(" 4", v) }
if case .some = optVal { print(" 5" ) }
if case .some(let v) = optVal { print(" 6", v) }
if let v = optVal { print(" 7", v) }
if let _ = optVal { print(" 8" ) }
if optVal != .none { print(" 9" ) }
if optVal != nil { print("10" ) }