I think this problem definitely merits addressing – optional collections do happen in the real world and are a real pain when they aren't expressible by array literals – and I think the proposed syntax would be a good solution.
However, I think the proposal could do with giving more serious consideration to the alternative of a orEmpty
property on Optional<Sequence>
, as Joe described in his rejection of SE-0231.
The reasons for not considering it in this proposal are not compelling to me:
- Preserving symmetry with
forEach
:forEach
is a bug not a feature (IMO at least :) - Discoverability: if
.orEmpty
is in the std lib, the compiler can produce a fixit to use it when attempting to loop over an optional.
Here are the pros/cons of the two options as I see them:
Pro for x in y?
:
- follows existing practice of sugaring optional chaining with
?
- very succinct while (arguably) clear given the existing language conventions
- potentially even more succinct if "chained" e.g.
for x in try? y
does not require parens unlikefor x in (try? f()).orEmpty
which is pretty noisy
Pro for for x in y.orEmpty
:
- easily searchable
- can be implemented in the library today without compiler magic
- (arguably) clearer
- discoverable via completion (tenuous, especially if the fixit idea is pursued)
Implementation of orEmpty if you want to play with it
struct MaybeSequence<Wrapped: Sequence> {
let wrapped: Wrapped?
}
extension MaybeSequence {
struct Iterator { var wrapped: Wrapped.Iterator? }
}
extension MaybeSequence.Iterator: IteratorProtocol {
mutating func next() -> Wrapped.Element? { wrapped?.next() }
}
extension MaybeSequence: Sequence {
func makeIterator() -> Iterator { .init(wrapped: wrapped?.makeIterator()) }
}
extension Optional where Wrapped: Sequence {
var orEmpty: MaybeSequence<Wrapped> { .init(wrapped: self) }
}