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.
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.
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 thefor
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.
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 ?? [] { ... }
- 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 { … }
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:
-
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. -
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 andnil
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.
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 ~ edit: doh, of course there are – e.g. ?
is used with a keyword rather than a type or a variable.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.
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()
.
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.
-
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 preferin?
rather thanfor?
and why not justoptional?.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.
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?
for x in xs? {}
indeed is a more consistent, and obvious syntax. I'd go from -1 to -0.9 with this change :P
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.