SE-0008: Add a Lazy flatMap for Sequences of Optionals #
* Proposal: [SE-0008](0008-lazy-flatmap-for-optionals.md) * Author: [Oisin Kidney](https://github.com/oisdk) * Review Manager: [Doug Gregor](https://github.com/DougGregor) * Status: **Implemented (Swift 3.0)** * Decision Notes: [Rationale](https://forums.swift.org/t/accepted-se-0008-add-a-lazy-flatmap-for-sequences-of-optionals/748) * Bug: [SR-361](https://bugs.swift.org/browse/SR-361)
Introduction ##
Currently, the Swift standard library has two versions of flatMap. One which flattens a sequence of sequences after a transformation:
[1, 2, 3]
.flatMap { n in n..<5 }
// [1, 2, 3, 4, 2, 3, 4, 3, 4]And another which flattens a sequence of Optionals:
(1...10)
.flatMap { n in n % 2 == 0 ? n/2 : nil }
// [1, 2, 3, 4, 5]However, there is only a lazy implementation for the first version:
[1, 2, 3]
.lazy
.flatMap { n in n..<5 }
// LazyCollection<FlattenBidirectionalCollection<LazyMapCollection<Array<Int>, Range<Int>>>>
(1...10)
.lazy
.flatMap { n in n % 2 == 0 ? n/2 : nil }
// [1, 2, 3, 4, 5]Swift Evolution Discussions: Lazy flatMap for Optionals, Review
Motivation ##
Seeing as the already-existing flatMap has a lazy version for nested sequences, a missing lazy version for sequences of Optionals seems like a gap. The usefulness of lazy sequences is well documented, especially when refactoring imperative nested for-loops into chains of methods, which can unnecessarily allocate intermediate arrays if done eagerly.
Proposed Approach ##
Making use of already-existing types in the standard library, flatMap's functionality can be achieved with a map-filter-map chain:
extension LazySequenceType {
@warn_unused_result
public func flatMap<T>(transform: Elements.Generator.Element -> T?)
-> LazyMapSequence<LazyFilterSequence<LazyMapSequence<Elements, T?>>, T> {
return self
.map(transform)
.filter { opt in opt != nil }
.map { notNil in notNil! }
}
}Detailed Design ##
A version for LazyCollectionTypes is almost identical:
extension LazyCollectionType {
@warn_unused_result
public func flatMap<T>(transform: Elements.Generator.Element -> T?)
-> LazyMapCollection<LazyFilterCollection<LazyMapCollection<Elements, T?>>, T> {
return self
.map(transform)
.filter { opt in opt != nil }
.map { notNil in notNil! }
}
}However, a "bidirectional" version cannot be written in this way, since no FilterBidirectionalCollection exists.
The other form of flatMap uses a flatten method on nested sequences, which has both a CollectionType form and a form for CollectionTypes with BidirectionalIndexTypes.
However, Swift's current type system doesn't allow a similar method to be defined on sequences of Optionals. This means we have to rely on filter, which only has a SequenceType and CollectionType implementation.
Alternatives considered ##
Custom struct ###
It would also be possible to add a new struct, and a method on LazySequenceType:
public struct FlatMapOptionalGenerator<G: GeneratorType, Element>: GeneratorType {
private let transform: G.Element -> Element?
private var generator: G
public mutating func next() -> Element? {
while let next = generator.next() {
if let transformed = transform(next) {
return transformed
}
}
return nil
}
}
public struct FlatMapOptionalSequence<S: LazySequenceType, Element>: LazySequenceType {
private let transform: S.Generator.Element -> Element?
private let sequence: S
public func generate() -> FlatMapOptionalGenerator<S.Generator, Element> {
return FlatMapOptionalGenerator(transform: transform, generator: sequence.generate())
}
}
extension LazySequenceType {
public func flatMap<T>(transform: Generator.Element -> T?) -> FlatMapOptionalSequence<Self, T> {
return FlatMapOptionalSequence(transform: transform, sequence: self)
}
}However, this implementation does not have a LazyCollectionType version. To add one, and a bidirectional implementation, six new types (three SequenceTypes, three GeneratorTypes) would have to be added to the standard library.
New Filter struct ###
This would involve adding a FilterBidirectionalCollection to the standard library. Arguably, this is a gap currently. It would allow both flatMap versions to mirror each other, with minimal new types.
Make Optional Conform to SequenceType ###
This is a far-reaching, separate proposal, but it would solve the issue that this proposal seeks to solve. It's worth bearing in mind, though, that Optional probably wouldn't have a BidirectionalIndexType, so the bidirectional version of flatMap wouldn't exist on Optionals, anyway.