I was thinking we should do this as well, though for a completely different reason:
Motivation
The current lazy system is very good for easily defining transforms on collections, but certain constructs can lead to less-than-optimal codegen.
For example, the code collection.map(map1).filter(filter1).map(map2).filter(filter2) will lead to code like this in the formIndex(after:) method:
do {
do {
collection.formIndex(after: &i)
} while !filter1(map1(collection[i]))
} while !filter2(map2(map1(collection[i])))
while it could be represented with this more efficient single loop:
do {
collection.formIndex(after: &i)
} while !filter1(map1(collection[i])) && !filter2(map2(map1(collection[i])))
Currently, you can get a single loop by instead using this compactMap:
collection.compactMap {
let a = map1($0)
guard filter1(a) else { return nil }
let b = map2(a)
guard filter2(b) else { return nil }
return b
}
but this removes the nice composability of the chained map/filter combination.
The standard library recently got an override on LazyMapCollection and LazyFilterCollection which combines multiple filters and maps in a row, however it does not work with alternating maps and filters.
Solution
Define a LazyCompactMapCollection collection (and sequence) which represents a compactMap. Then, add overrides on LazyMapCollection.filter, LazyFilterCollection.map, Lazy*Collection.compactMap, and LazyCompactMapCollection.{filter, map} to return a LazyCompactMapCollection that combines all the maps and filters.
A demo implementation is available here, use .lazy2 to get the new implementation.
Note: Requires Swift 4.1 due to it being based off of the current stdlib code
As an added bonus, you'll never see a giant chain of LazyMapCollection<LazyFilterCollection<...>, ...> again
Note
As this would change the type returned by some methods, this would be a source-breaking change, though since I think .lazy is usually used as an intermediary step between a collection and some aggregator, most uses would probably be fine.
Other Note
The new implementation does perform slightly worse in Debug builds, however using .lazy in a debug build already performs much worse than an equivalent loop (compared to release where the new implementation performs almost as well as the loop), so I'm not sure if this really matters.