Hello Swift Evolution,
In playing around with the standard library’s Sequence types, I noticed an unexpected behaviour.
The current implementation of LazyFilterCollection
uses the indices of the base
collection as its own, and also forwards any subscripts directly to the base collection. The relevant implementation is as shown below:
extension LazyFilterCollection: Collection {
public subscript(position: Index) -> Element {
return _base[position]
}
public subscript(bounds: Range<Index>) -> SubSequence {
return SubSequence(_base: _base[bounds], _predicate)
}
}
This means that it is possible to retrieve values via subscripting that shouldn’t exist in the filtered collection:
let evenDigits = Array(0 ..< 10).lazy.filter { $0.isMultiple(of: 2) }
print(evenDigits[3]) // prints 3
And it also means that indices which don’t exist in the filtered collection can be subscripted:
let evenDigits = Array(0 ..< 10).lazy.filter { $0.isMultiple(of: 2) }
print(evenDigits[5]) // prints 5
print(Array(eventDigits[5...])) // prints [6, 8]
This behaviour isn’t currently documented.
Additionally since Sequence operators are often used in a chained fashion (lazy.compactMap
, for instance, is implemented as lazy.map.filter.map
) it would be hard to debug any errors caused by the existing subscripting behaviour, and especially with API vending type-erased or opaque collections, consumers might not even be aware that a lazy filter is being used under the hood, unless the creator decides to explicitly leak and maintain that implementation detail.
As for possible solutions, I have a draft Swift Evolution proposal that introduces a new LazyFilterCollection.Index
subtype which handles the base index internally, with the working being similar to String.Index
, with the init
being private, and construction being limited to querying a given lazy filtered collection instance using the index(after:)
, index(before:)
etc. methods. That proposal, however, would break the ABI.
I’m not sure what other possible solutions there are that can also work efficiently given the lazy execution model and also preserve ABI compatibility, so I’m curious to hear the forum and standard library team’s take on this problem.