點燈坊

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

使用 transduce() 加速 Function Pipeline

Sam Xiao's Avatar 2020-12-03

FP 常被人詬病效率問題,若使用 pipe() 組合了 3 個 Function,由於 Pure Function 每次都回傳全新 Array,因此要執行三次 Loop,在 Array 筆數大時有明顯劣勢,透過 transduce() 只要執行一次 Loop 即可。

Version

Ramda 0.27.1

Function Pipeline

import { pipe, take, map, add } from 'ramda'

let data = [1, 2, 3]

pipe(
  map(add(1)),
  take(2)
)(data) // ?
  • 使用 add() 對 Array 每個 element 加 1
  • 使用 take() 只取得前兩個 element

pipe() 組合了 map()take() 兩個 function,會執行兩次 loop,若 Array 資料量大會明顯發現執行效率不佳

transduce000

Transducer

import { compose, take, map, add, transduce, append } from 'ramda'

let data = [1, 2, 3]

let f = compose(
  map(add(1)),
  take(2)
)

transduce(f, (ac, x) => append(x, ac), [], data) // ?

第 5 行

let f = compose(
  map(add(1)),
  take(2)
)

要使用 transduce() 時,首先將原本 function 從 pipe() 改成 compose()

10 行

transduce(f, (ac, x) => append(x, ac), [], data) // ?

transduce() 的 signature 與 reduce() 類似,唯第一個 argument 改傳入 function,其餘 argument 都相同。

transduce()
(c → c) → ((a, b) → a) → a → [b] → a
回傳只執行一次 loop 的 function

c -> c:由 compose() 改寫的 function

(a, b) → a:iterator function,運算 accumulator 與 value 關係

a:accumulator 的初始值

[b]:data 為 array

a:回傳為新 array

transduce001

flip()

import { compose, take, map, add, transduce, flip, append } from 'ramda'

let data = [1, 2, 3]

let f = compose(
  map(add(1)),
  take(2),
)

transduce(f, flip(append), [], data) // ?

10 行

transduce(f, flip(append), [], data) // ?

Iterator function 的 signature 剛好跟 append() 相反,可使用 flip(append) 使其 Point-free。

transduce002

Conclusion

  • FP 常為人詬病執行效率不佳,透過 transduce() 可改善此問題
  • 原使用 pipe() 所組合 function 須改用 compose()
  • Iterator function 可由 flip(append) 使其 Point-free

Reference

Jeremy Daly, Transducers: Supercharge your functional JavaScript
James Long, Transducer.js: A JavaScript Library for Transformation of Data