When lazily mapping an array of values, I receive an instance of type LazyMapSequence
as expected:
Welcome to Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50).
Type :help for assistance.
1> let numbers = Array(1...5)
numbers: [Int] = 5 values {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
[4] = 5
}
2> let squares = numbers.lazy.map { $0 * $0 }
squares: LazyMapSequence<LazySequence<[Int]>.Elements, Int> = {
_base = 5 values {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
[4] = 5
}
_transform =
}
However, if the map(_:)
method receives a throwing a closure instead, the mapping is not performed lazily, and I receive an array instead:
3> func square(_ x: Int) throws -> Int {
4. return x * x
5. }
6> let squares = try numbers.lazy.map(square)
squares: [Int] = 5 values {
[0] = 1
[1] = 4
[2] = 9
[3] = 16
[4] = 25
}
Why is that, and how do I lazily map an array of values using a throwing closure?
A workaround is
extension LazySequennce {
func tryMap<U>(_ transform: @escaping (Self.Element) throws -> U) -> LazyMapSequence<Self.Elements, Result<U, Error>> {
self.map { x in Result(catching: { try transform(x) }) }
}
}
Note that the element type of the sequence is Result<U, Error>
. We essentially "catch" the error whenever any is thrown. The error has to be caught because when iterating over any Sequence
, the protocol requires that no errors are thrown.
As for why map(square)
is not lazy, it is exactly as you have observed. LazySequenceProtocol.map
takes a closure that does not throw.
func map<U>(_ transform: @escaping (Self.Element) -> U)
-> LazyMapSequence<Self.Elements, U>
When you pass in the throwing method, it instead calls Sequence.map
:
func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]
which is not lazy.
This would all be solved if there were a method that looked like:
func tryMap<U>(_ transform: @escaping (Self.Element) throws -> U)
-> LazyThrowingMapSequence<Self.Elements, U>
However, such a LazyThrowingMapSequence
type cannot conform to Sequence
, because its iterator cannot conform to IteratorProtocol
. Its iterator's next
method throws, but IteratorProtocol
requires that next
does not throw.
It is theoretically possible to write LazyThrowingMapSequence
by just adding throws
to a few places to LazyMapSequence
. (Source code of LazyMapSequence
is here) But using it would be a pain, since you cannot iterate over it with a for
loop, and it doesn't have any of those convenient methods from the Sequence
protocol.