Home > Net >  Lazily Mapping an Array Using a Throwing Closure
Lazily Mapping an Array Using a Throwing Closure

Time:01-06

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?

CodePudding user response:

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.

If that is okay for you, it is possible to write LazyThrowingMapSequence by just adding throws to a few places to LazyMapSequence. Source code of LazyMapSequence is here.

Alternatively, you can add a method with the signature:

func tryMap<U>(_ transform: @escaping (Self.Element) throws -> U)
    -> LazyMapSequence<Self.Elements, Result<U, Error>>

Note that the element type of the sequence is Result<U, Error>. We essentially "catch" the error whenever any is thrown. You can implement it simply by:

self.map { x in Result(catching: { try transform(x) }) }
  • Related