- 原文地址:Reducers VS Transducers
- 原文做者:Maksim Ivanov
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:jonjia
- 校對者:allenlongbaobao leviding
今天咱們爲您準備了一份函數範式甜點。我也不知道爲何會用「VS」,並且它倆還互相恭維。無論那麼多了,讓咱們看點好東西......前端
簡單來講,Reducer
就是個接收上一個計算值和一個當前值並返回新的計算值的方法。android
若是你使用過數組的 Array.prototype.reduce()
方法,就已經熟悉了 reducer。數組的 .reduce()
方法自己並非一個 reducer,這個方法會遍歷一個集合(譯註:累加器初始值和數組中的元素組成的集合),而後對集合中的每一個元素應用傳給這個方法的回調函數,這個回調函數纔是一個 reducer。ios
假設咱們有一個包含五個數字的數組:[1, 2, 3, 14, 21]
,咱們要找出它們中的最大值。git
const numbers = [1, 2, 3, 14, 21];
const biggestNumber = numbers.reduce(
(accumulator, value) => Math.max(accumulator, value)
);
// 21
複製代碼
這裏的箭頭函數就是一個 reducer。數組的 .reduce()
方法只是取這個 reducer 上一次執行的結果(譯註:初始值參數或數組第一個元素)和數組中的下一個元素傳給並繼續調用這個 reducer。github
Reducers 能夠處理任何類型的值。惟一條件就是計算方法返回的值類型和傳給計算方法的值類型要保持一致。後端
在下面的例子中,你能夠輕鬆建立一個做用於字符串的 reducer:數組
const folders = ['usr', 'var', 'bin'];
const path = folders.reduce(
(accumulator, value) => `${accumulator}/${value}`
, ''); // Here I passed empty string as an initial value
// /usr/var/bin
複製代碼
實際上,不使用 Array.reduce()
方法來講明更好理解。以下:bash
const stringReducer = (accumulator, value) => `${accumulator} ${value}`
const helloWorld = stringReducer("Hello", "world!")
// Hello world!
複製代碼
Reducers 還有一個好處是你能夠鏈式地鏈接它們,來實現對某些數據的一系列操做。這就爲功能模塊化和 reducer 的複用提供了巨大的可能。數據結構
假設有一個有序的數字數組。你想獲取其中的偶數,而後再乘以 2。app
實現上述功能一般的方法是調用數組的 .map
和 .filter
方法:
[1, 2, 3, 4, 5, 6]
.filter((x) => x % 2 === 0)
.map((x) => x * 2)
複製代碼
但若是這個數組有 1000000 個元素呢?你須要遍歷整個數組的每一個元素,這樣的效率過低了。
咱們須要用某種方式去組合傳給 map
和 filter
方法的函數。由於它們的接口不一樣,因此咱們沒法實現。傳給 filter
方法的函數稱爲斷言函數,它接收一個值,依據內部邏輯返回斷言的 True 或者 False。傳給 map
方法的函數稱爲轉換函數,它接收一個值,並返回轉換後的值。
咱們能夠經過 reducers 來實現這一點,讓咱們建立本身的 reducer 版本的 .map
和 .filter
方法。
const filter = (predicate) => {
return (accumulator, value) => {
if(predicate(value)){
accumulator.push(value);
}
return accumulator;
}
}
const map = (transformer) => {
return (accumulator, value) => {
accumulator.push(transformer(value));
return accumulator;
}
}
複製代碼
真棒,咱們使用了 裝飾器 來包裝咱們的 reducers。如今咱們有本身的 map
和 filter
方法,它們返回的 reducers 能夠傳遞給數組的 Array.reduce()
方法。
[1, 2, 3, 4, 5, 6]
.reduce(filter((x) => x % 2 === 0), [])
.reduce(map((x) => x * 2), [])
複製代碼
太棒了,如今咱們就能鏈式地調用一系列的 .reduce
方法,但咱們仍是沒有組合咱們的 reducers!好消息是咱們只差一步了。爲了能組合 reducers 咱們須要讓它們能互相傳遞。
來升級下咱們的 filter
方法,讓它可以接收 reducers 做爲參數。咱們要分解下它,不是將值添加到 accumulator,而是要傳給傳入的 reducer,並執行這個 reducer。
const filter = (predicate) => (reducer) => {
return (accumulator, value) => {
if(predicate(value)){
return reducer(accumulator, value);
}
return accumulator;
}
}
複製代碼
咱們接收一個 reducer 做爲參數,並返回另外一個 reducer 的這種模式就叫作 transducer。它是 transformer 和 reducer 的結合(咱們接收一個 reducer,並對它進行了轉換)。
const transducer => (reducer) => {
return (accumulator, value) => {
// 轉換 reducer 的邏輯
}
}
複製代碼
因此最基礎的 transducer 就像 (oneReducer) => anotherReducer
這樣。
如今咱們就能夠組合使用咱們的 mapping reducer 和 filtering transducer,一次調用就能夠實現咱們的計算了。
const evenPredicate = (x) => x % 2 === 0;
const doubleTransformer = (x) = x * 2;
const filterEven = filter(evenPredicate);
const mapDouble = map(doubleTransformer);
[1, 2, 3, 4, 5, 6]
.reduce(filterEven(mapDouble), []);
複製代碼
實際上,咱們也能夠把咱們的 map 方法改造爲一個 transducer,而後無限地繼續這種改造。
但若是要組合 2 個以上的 reducers 呢?咱們要找到更簡便的組合方法。
整體來講就是,咱們須要一個能接收必定數量的函數並把它們按順序組合的方法。相似下面這樣:
compose(fn1, fn2, fn3)(x) => fn1(fn2(fn3(x)))
複製代碼
幸運的是,不少庫都提供了這種功能。好比 RamdaJS 這個庫。但爲了解釋清楚,來建立咱們本身的版本吧。
const compose = (...functions) =>
functions.reduce((accumulation, fn) =>
(...args) => accumulation(fn(args)), x => x)
複製代碼
這個函數的功能很是緊湊,咱們來分解下。
若是咱們像這樣 compose(fn1, fn2, fn3)(x)
調用了這個函數。
首先看 x => x
部分。在 λ 演算中,這被稱爲 恆等函數。無論接收什麼參數,它都不會改變。咱們就從這裏展開。
因此在第一次遍歷中,咱們將使用 fn1 函數做爲參數來調用 identity function 函數(爲了方便,咱們稱之爲 I):
// 恆等函數:I
(...args) => accumulation(fn(args))
// 第一步
// 咱們把 fn1 傳給 accumulation 方法
(...args) => accumulation(fn1(args))
// 第二步
// 這裏咱們用 I 接收 fn1 做爲參數替代 accumulation
(...args) => I(fn1(args))
複製代碼
耶,咱們計算出了第一次遍歷後新的 accumulation
方法。咱們再來一次:
(...args) => I(fn1(args)) // 新的 accumulation 方法
// 第三步
// 如今咱們把 fn2 傳給 accumulation 方法
(...args) => accumulation(fn2(args))
// 第四步
// 咱們來算出 accumulation 的當前值
(...args) => I(fn1(fn2(args)))
複製代碼
我認爲你應該理解了。如今只須要對 fn3
重複第三步和第四步,就能夠把 compose(fn1, fn2, fn3)(x)
轉爲 fn1(fn2(fn3(x)))
了。
最後咱們就能夠像下面這樣組合咱們的 map
和 filter
了:
[1, 2, 3, 4, 5, 6]
.reduce(compose(filterEven,
mapDouble));
複製代碼
我想你已經掌握了 reducers,若是尚未 — 你也已經學會了處理集合的抽象方法。Reducers 能夠處理不一樣的數據結構。
你也學會了如何用 transducers 有效地進行計算。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。