;; reducing function signature
whatever, input -> whatever
減少函數是您傳遞給 reduce 的函數類型 - 它是一個函數,它接受累積結果和新的輸入,並傳回新的累積結果
;; reducing function signature
whatever, input -> whatever
轉換器(有時稱為 xform 或 xf)是從一個減少函數到另一個減少函數的轉換
;; transducer signature
(whatever, input -> whatever) -> (whatever, input -> whatever)
Clojure 中包含的大多數序列函數都具有產生轉換器的元數。此元數省略輸入集合;輸入將由套用轉換器的程序提供。注意:此減少的元數不是柯里化或部分應用。
例如
(filter odd?) ;; returns a transducer that filters odd
(map inc) ;; returns a mapping transducer for incrementing
(take 5) ;; returns a transducer that will take the first 5 values
轉換器與一般函數組合組合。轉換器在決定是否以及呼叫它封裝的轉換器多少次之前執行其操作。建議使用現有的 comp 函數組合轉換器
(def xf
(comp
(filter odd?)
(map inc)
(take 5)))
轉換器 xf 是轉換堆疊,程序會將其應用於一系列輸入元素。堆疊中的每個函數在它封裝的操作之前執行。轉換器的組合從右到左執行,但會建立從左到右執行的轉換堆疊(在此範例中,篩選發生在對應之前)。
作為助記符,請記住 comp 中轉換器函數的順序與 ->> 中序列轉換的順序相同。以上的轉換等於序列轉換
(->> coll
(filter odd?)
(map inc)
(take 5))
下列函數在省略輸入集合時會產生轉換器:map cat mapcat filter remove take take-while take-nth drop drop-while replace partition-by partition-all keep keep-indexed map-indexed distinct interpose dedupe random-sample
應用轉換器最常見的方法之一是使用 transduce 函數,它類似於標準的 reduce 函數
(transduce xform f coll)
(transduce xform f init coll)
transduce 會立即(非延遲)使用應用於還原函數 f 的轉換器 xform 還原 coll,如果提供 init 則使用 init 作為初始值,否則使用 (f)。f 提供如何累積結果的知識,這發生在還原的(潛在有狀態的)環境中。
(def xf (comp (filter odd?) (map inc)))
(transduce xf + (range 5))
;; => 6
(transduce xf + 100 (range 5))
;; => 106
組合的 xf 轉換器將從左到右呼叫,最後呼叫還原函數 f。在最後一個範例中,輸入值會先過濾,然後遞增,最後求和。
若要擷取將轉換器應用於 coll 的程序,請使用 eduction 函數。它採用任意數量的 xform 和最終 coll,並傳回轉換器對 coll 中項目的可還原/可迭代應用。每次呼叫 reduce/iterator 時,都會執行這些應用。
(def iter (eduction xf (range 5)))
(reduce + 0 iter)
;; => 6
若要從換能器的應用程式建立輸入集合的序列,請使用 sequence
(sequence xf (range 1000))
結果序列元素會逐步計算。這些序列會視需要逐步使用輸入,並完全實現中介運算。此行為與惰性序列上的等效運算不同。
換能器的形狀如下(自訂程式碼為「…」)
(fn [rf]
(fn ([] ...)
([result] ...)
([result input] ...)))
許多核心序列函數(例如 map、filter 等)會採用特定於運算的引數(謂詞、函數、計數等),並傳回封閉在這些引數中的此形狀的換能器。在某些情況下,例如 cat,核心函數就是換能器函數,且不會採用 rf。
內部函數定義有 3 個用於不同目的的 arity
Init(arity 0) - 應呼叫巢狀轉換 rf 上的 init arity,最終會呼叫出換能器處理程序。
Step(arity 2) - 這是標準的簡約函數,但預期會在換能器中適當地呼叫 rf step arity 0 次或更多次。例如,filter 會根據謂詞選擇是否呼叫 rf。map 會永遠只呼叫一次。cat 會根據輸入呼叫多次。
Completion(arity 1) - 有些處理程序不會結束,但對於那些會結束的處理程序(例如 transduce),completion arity 會用於產生最終值和/或清除狀態。此 arity 必須呼叫 rf completion arity 一次。
completion 的範例用途為 partition-all,它必須在輸入結束時清除所有剩餘元素。 completing 函數可用於透過新增預設完成 arity 來將簡約函數轉換為換能器函數。
Clojure 有指定簡約提早終止的機制
使用換能器的處理程序必須檢查並在 step 函數傳回簡約值時停止(在建立可換能處理程序中會詳細說明)。此外,使用巢狀簡約的換能器 step 函數必須在遇到簡約值時檢查並傳達簡約值。(請參閱 cat 的實作以取得範例。)
某些轉換器(例如 take、partition-all 等)在還原過程中需要狀態。每次可轉換的過程套用轉換器時,都會建立此狀態。例如,考慮將一系列重複值壓縮成單一值的 dedupe 轉換器。此轉換器必須記住前一個值,以確定是否應傳遞目前的數值
(defn dedupe []
(fn [xf]
(let [prev (volatile! ::none)]
(fn
([] (xf))
([result] (xf result))
([result input]
(let [prior @prev]
(vreset! prev input)
(if (= prior input)
result
(xf result input))))))))
在 dedupe 中,prev 是在還原期間儲存前一個值的狀態容器。prev 值是為了效能而設的暫存變數,但它也可以是原子。prev 值會在轉換過程開始之前初始化(例如在呼叫 transduce 時)。因此,狀態互動會包含在可轉換過程的內容中。
在完成步驟中,具有還原狀態的轉換器應在呼叫嵌套轉換器的完成函式之前清除狀態,除非它先前已從嵌套步驟中看到已還原的值,否則應捨棄待處理的狀態。