[Pitch] Extending optional chains to include for loops

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 unlike for 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) }
}
5 Likes