點燈坊

失くすものさえない今が強くなるチャンスよ

如何正確使用 reduce() ?

Sam Xiao's Avatar 2020-01-15

reduce() 為 FP 代表性 Function,很多人覺得 redurce() 沒有 Imperative 的 for Loop 直覺,因而避而遠之,事實上只是沒用對 reduce() 而已 。

Version

macOS Catalina 10.15.2
VS Code 1.40.2
Quokka 1.0.259
Ramda 0.26.1

Imperative

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let fn = val => arr => {
  let result = 0

  for(let x of arr) {
    if (x.title.includes(val))
      result += x.price
  }

  return result
}

fn('JavaScript')(data) // ?

若我們想找出所有 title 包含 JavaScript 字眼的書籍的 price 總和。

Imperative 會在 for loop 內使用 if 判斷 title 是否包含 JavaScript,若包含則加總 price,最後回傳結果。

reduce000

Wrong Way

import { reduce } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let fn = val => reduce((a, x) => {
  if (x.title.includes(val)) 
    return a + x.price
  else
    return a
}, 0)

fn('JavaScript')(data) // ?

很多人會直接將 for loop 的邏輯搬進 reduce() 內,結果雖然正確,但這並不是 reduce() 最佳用法。

reduce001

Right Way

import { map, filter, reduce, pipe } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let fn = val => pipe(
  filter(x => x.title.includes(val)),
  map(x => x.price),
  reduce((a, x) => a + x, 0)
)

fn('JavaScript')(data) // ?

FP 精神簡單來說就是:將問題最小化各個擊破,再加以組合

可將問題拆成 3 個步驟:

  • 使用 filter() 找出 titleJavaScript 書籍
  • 使用 map() 找出所有書籍的 price
  • 最後將 price 加總

最後一個步驟因為不知道用什麼 function,只好用 reduce() 實現。

reduce() 正確用法並不是將一個大大 function 放進 reduce(),而是先拆解問題以既有 function 解決,若仍無法處理才會使用 reduce(),最後再組合所有 function,也就是 reduce() 不該搭配一個大大 function

reduce002

import { map, filter, pipe, sum } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let fn = val => pipe(
  filter(x => x.title.includes(val)),
  map(x => x.price),
  sum
)

fn('JavaScript')(data) // ?

隨著經驗累積,可能會發現有其他 function 能解決問題,或者可自行用 reduce() 建立新 function,此時可用該 function 取代 reduce()

reduce003

Conclusion

  • reduce() 應該只用來處裡單純一件事情,因此都搭配小 function,若 reduce() 搭配大 function,則表示該 function 一定能再拆解使用小 function 處理
  • 應該盡量先使用既有 function 解決問題,直到無法處理才會使用 reduce(),實務上使用 reduce() 機會沒這麼大,因為如 Ramda、Crocks 都已經累積足夠 function 可用,reduce() 只用來處理特殊部分,最後再組合所有 function,或者由 reduce() 建立新的 function,供日後組合使用