reduce() vs. reduce(into:)

`reduce(_:_:)``reduce(into:_:)`

  • 參數不同: reduce(into:_:)inout parameterreduce(_:_:)沒有,所以當它將之前累積的結果傳入 nextPartialResult 並需要更新新的累積結果時,就必須複製 (copy-on-write) 之前的結果,所以如果 Result 是一個大型的 ArrayDictionary 之類的型別,就會對程式的效率產生衝擊

  • 回傳方式不同:

    reduce(_:_:) 是將 nextPartialResult 最後的 return value 作為整個方法的回傳值reduce(into:_:) 則是用 updateAccumulatingResult 持續對 partialResult更新,然後以 partialResult 最後的值作為回傳值

所以使用reduce(into:_:)時要注意:「一定要透過 updateAccumulatingResult 來更新 partialResult」,否則回傳值會是原來的 initialResult

// `reduce(_:_:)`
func reduce<Result>(
    _ initialResult    : Result, 
    // ⭐️ combine function (with return value)
    _ nextPartialResult: (Result, Element) throws -> Result
) rethrows -> Result

// `reduce(into:_:)`
func reduce<Result>(
    into initialResult: Result, 
    _ updateAccumulatingResult: (
        _ partialResult: inout Result,    // ⭐️ inout parameter
        Self.Element    
    ) throws -> ()                        // ⭐️ no return value
) rethrows -> Result

.reduce(into:) is preferred over reduce() for efficiency when the result is a copy-on-write type, for example an Array or a Dictionary. 👉 .reduce(into:_:)

Example

我們用合併 SwiftUI 中的 transitions 來舉例,說明這兩者的用法有哪些不同。在合併 SwiftUI 中的 transitions 時,使用 SwiftUI 原有的語法,顯得太過冗長:

AnyTransition
    .move(edge: .leading)
    .combined(with: .opacity)
    .combined(with: .scale)

若使用 .reduce() 或 .reduce(into:),則可以將語法變為更精簡:

// 無法自行推斷 (infer) AnyTransition 型別的時候:
AnyTransition(.move(edge: .leading), .opacity, .scale)

// 可以自行推斷 AnyTransition 型別的時候:
.combined(.move(edge: .leading), .opacity, .scale)

前提是要先加入以下的 extension:

extension AnyTransition {
    
    // ⭐️ combining transitions
    // AnyTransition([.scale, .opacity, ...]) ........ (1)
    init(_ transitions: [AnyTransition]) {
        // use reduce() or reduce(into:)
    }
    
    // 讓可以使用的語法更豐富,在可以使用 AnyTransition 的地方,直接用:
    // .combined(.move, .scale, ...)
    public static func combined(_ transitions: AnyTransition...) -> AnyTransition {
        .init(transitions)
    }
}

extension AnyTransition: ExpressibleByArrayLiteral {
    // convenience init
    // AnyTransition(.move, .scale, .opacity, ...)
    public init(arrayLiteral elements: AnyTransition...) {
        self.init(elements)    // 使用 (1)
    }
}

至於用 .reduce() 還是 .reduce(into:)?它們之間有何不同,請看下面的程式碼:

.reduce()

init(_ transitions: [AnyTransition]) {
    let intialValue = AnyTransition.identity
    // ⭐️ 使用 `reduce(_:_)`
    self = transitions.reduce(intialValue) { (result, transition) in
        // ⭐️ 下面的結果會「自動成為新的 `result`」,不需手動改寫 `result`
        //    所以官方稱這個部分的 closure 為:`nextPartialResult`。
        result.combined(with: transition)    // ⭐️ implicit return
    }
}

.reduce(into:)

init(_ transitions: [AnyTransition]) {
    var intialValue = AnyTransition.identity
    // ⭐️ 使用 `reduce(into:_:)`
    self = transitions.reduce(into: intialValue) { (result, transition) in
        // ⭐️ 記得手動改寫 `result` 變數,否則效果並不會累加。
        //    因為如果我們不寫 `result = ...`,這時 `result` 變數
        //    並不會自動改變,所以官方稱這個部分為 `updateAccumulatingResult`
        //    而不是 `nextPartialResult`。
        result = result.combined(with: transition)    // ⭐️ no return value
    }
}

Last updated