Swift LazySequence

«  User Privacy and Data Use
Add Blog Comments By Github Issues API  »

LazySequence

A sequence containing the same elements as a Base sequence, but on which some operations such as map and filter are implemented lazily.

LazySequenceProtocol

A sequence on which normally-eager sequence operations are implemented lazily.

Lazy sequences can be used to avoid needless storage allocation and computation, because they use an underlying sequence for storage and compute their elements on demand.

ex:

let doubled = [1, 2, 3].lazy.map { $0 * 2 }
// return [2, 4, 6]

Sequence operations that take closure arguments, such as map(:) and filter(:), are normally eager: They use the closure immediately and return a new array. When you use the lazy property, you give the standard library explicit permission to store the closure and the sequence in the result, and defer computation until it is needed.

It would be helpful if we want to check things quickly. ex:

let doubled = [1, 2, 3].lazy.map { num -> Int in
    print("=== M \(num)")
    return num * 2
}.first
// print --> === M 1
// return 2
// the later 2, 3 will not come into map, because `first` already means this loop is finished

Add New Lazy Operations

To add a new lazy sequence operation, extend this protocol with a method that returns a lazy wrapper that itself conforms to LazySequenceProtocol.

extension Sequence {
    /// Returns an array containing the results of
    ///
    ///   p.reduce(initial, nextPartialResult)
    ///
    /// for each prefix `p` of `self`, in order from shortest to
    /// longest. For example:
    ///
    ///     (1..<6).scan(0, +) // [0, 1, 3, 6, 10, 15]
    ///
    /// - Complexity: O(n)
    func scan<Result>(
        _ initial: Result,
        _ nextPartialResult: (Result, Element) -> Result
    ) -> [Result] {
        var result = [initial]
        for x in self {
            result.append(nextPartialResult(result.last!, x))
        }
        return result
    }
}

// build a sequence type that lazily computes the elements in the result of a scan:
struct LazyScanSequence<Base: Sequence, Result>
    : LazySequenceProtocol
{
    let initial: Result
    let base: Base
    let nextPartialResult:
        (Result, Base.Element) -> Result

    struct Iterator: IteratorProtocol {
        var base: Base.Iterator
        var nextElement: Result?
        let nextPartialResult:
            (Result, Base.Element) -> Result
        
        mutating func next() -> Result? {
            return nextElement.map { result in
                nextElement = base.next().map {
                    nextPartialResult(result, $0)
                }
                return result
            }
        }
    }
    
    func makeIterator() -> Iterator {
        return Iterator(
            base: base.makeIterator(),
            nextElement: initial as Result?,
            nextPartialResult: nextPartialResult)
    }
}

// give all lazy sequences a lazy scan(_:_:) method:
extension LazySequenceProtocol {
    func scan<Result>(
        _ initial: Result,
        _ nextPartialResult: @escaping (Result, Element) -> Result
    ) -> LazyScanSequence<Self, Result> {
        return LazyScanSequence(
            initial: initial, base: self, nextPartialResult: nextPartialResult)
    }
}

// With this type and extension method, you can call .lazy.scan(_:_:) on any sequence to create a lazily computed scan. The resulting LazyScanSequence is itself lazy, too, so further sequence operations also defer computation.

Without lazy, if we normally call the sequence function, the value will never unexpectedly dropped or deferred.

Reference

https://developer.apple.com/documentation/swift/lazysequence

https://developer.apple.com/documentation/swift/lazysequenceprotocol

Published on 14 Aug 2020 Find me on Facebook, Twitter!

«  User Privacy and Data Use
Add Blog Comments By Github Issues API  »

Comments

    Join the discussion for this article at here . Our comments is using Github Issues. All of posted comments will display at this page instantly.