點燈坊

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

10 個常用等效重構 Pattern

Sam Xiao's Avatar 2020-04-12

Point-free 後的 Function 常出現等效重構 Pattern,可再以其他 Function 取代,讓 Codebase 更為精簡。

Version

macOS Catalina 10.15.4
VS Code 1.44.0
Quokka 1.0.285
Ramda 0.27.0

pluck()

import { map, prop } from 'ramda'

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

let f = k => map(prop(k))

f('price')(data) // ?

若想從 array 只取出特定 property 成為新 array,直覺會使用 map()prop() 組合。

import { pluck } from 'ramda'

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

let f = pluck

f('price')(data) // ?

map(prop(k)) 可等效重構成 pluck(k)

pattern000

project()

import { map, pick } from 'ramda'

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

let f = map(pick(['title', 'price']))

f(data) // ?

若想從 array 只取出部分 property 成為新 array,直覺會使用 map()pick() 組合。

import { project } from 'ramda'

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

let f = project(['title', 'price'])

f(data) // ?

map(pick([k])) 可等效重構成 project([k])

pattern001

uniq()

import { uniqBy, identity } from 'ramda'

let data = [1, 2, 3, 1]

let f = uniqBy(identity)

f(data) // ? 

若想從 array 只取出不重複部分,直覺會使用 uniqBy()identity() 組合。

import { uniq } from 'ramda'

let data = [1, 2, 3, 1]

let f = uniq()

f(data) // ?

uniqBy(identity) 可等效重構成 uniq()

pattern002

tug()

import { pipeK, sum } from 'knex-fp'
import { pluck, head } from 'ramda'
import { log } from 'wink-fp'

pipeK(
  sum({ sum: 'price' })
)(
  pluck('sum'),
  head,
  log
)(mySQL('books'))

當使用 Knex 的 aggregate function,如 count()sum() 時,最後必須靠 Ramda 的 pluck()head() 組合才能取出值。

import { pipeK, sum } from 'knex-fp'
import { tug, log } from 'wink-fp'

pipeK(
  sum({ sum: 'price' })
)(
  tug('sum'),
  log
)(mySQL('books'))

pipe(pluck(k), head()) 的組合可等效重構成 tug(k)

pattern003

sum()

import { reduce } from 'ramda'

let data = [1, 2, 3]

let f = reduce((a, x) => a += x, 0)

f(data) // ?

若想將 array 中的 number 加總,直覺會使用 reduce()

import { sum } from 'ramda'

let data = [1, 2, 3]

let f = sum

f(data) // ?

reduce((a, x) => a += x, 0) 可等效重構成 sum()

pattern004

compose()

import { filter } from 'ramda'

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

let f = v => filter(x => x.price === v)

f(100)(data) // ?

若想將 array 透過 filter() 找出指定資料,會使 f() 傳進 value。

import { filter, useWith, identity, propEq } from 'ramda'

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

let f = useWith(
  filter, [propEq('price'), identity]
)

f(100)(data) // ?

若想將 f() 也 point-free,直覺會使用 useWith()identity() 組合。

import { filter, compose, propEq } from 'ramda'

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

let f = compose(filter, propEq('price'))
  
f(100)(data) // ?

useWith(f, [t, identity]) 可等效重構成 compose(f, t)

map()

import { filter, propEq } from 'ramda'
import { map } from 'crocks'

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

let f = map(filter, propEq('price'))
  
f(100)(data) // ?

若只有兩個 function 組合,則 compose(f, t) 亦等效於 map(f, t)

pattern005

chain()

import { pipe, map, flatten, inc } from 'ramda'

let data = [[1, 2], [3, 4], [5, 6]]

let f = pipe(
  map(map(inc)),
  flatten
)


f(data) // ?

若想將 nested array 的 element 都加 1,然後 flatten 為一層 array,直覺會使用兩層 map()flatten() 組合。

import { pipe, map, flatten, inc, chain } from 'ramda'

let data = [[1, 2], [3, 4], [5, 6]]

let f = chain(map(inc))

f(data) // ?

pipe(map(f), flatten) 可等效重構成 chain(f)

pattern006

import { append, head } from 'ramda'

let data = [1, 2, 3]

let f = a => append(head(a), a)

f(data) // ?

若想使用 append() 對 array 新增 element,且其值由 head() 取得。

import { append, head, converge, identity } from 'ramda'

let data = [1, 2, 3]

let f = converge(append, [head, identity])

f(data) // ?

若想對 f() point-free,直覺會使用 converge() 組合 append()head()identity()

import { append, head, chain } from 'ramda'

let data = [1, 2, 3]

let f = chain(append, head)

f(data) // ?

converge(f, [t, identity]) 可等效重構成 chain(f, t)

chain(f, t)(x) = f(t(x), x),當 f()t() 都需要 data,且 f() 的第一個 argument 必須先經過 t() 時,也適用於 chain()

pattern007

when()

import { map, ifElse, identity, inc } from 'ramda'
import { isEven } from 'wink-fp'

let data = [1, 2, 3]

let f = map(
  ifElse(isEven, inc, identity)
)

f(data) // ?

若想將 array 中 element 是 偶數+1奇數 不變,直覺會使用 ifElse()identity() 組合。

import { map, when, inc } from 'ramda'
import { isEven } from 'wink-fp'

let data = [1, 2, 3]

let f = map(
  when(isEven, inc)
)

f(data) // ?

ifElse(f, t, identity) 可等效重構成 when(f, t)

pattern008

unless()

import { map, ifElse, identity, inc } from 'ramda'
import { isEven } from 'wink-fp'

let data = [1, 2, 3]

let f = map(
  ifElse(isEven, identity, inc)
)

f(data) // ?

若想將 array 中 element 是 奇數+1偶數 不變,直覺會使用 ifElse()identity() 組合。

import { map, unless, inc } from 'ramda'
import { isEven } from 'wink-fp'

let data = [1, 2, 3]

let f = map(
  unless(isEven, inc)
)

f(data) // ?

ifElse(f, identity, t) 可等效重構成 unless(f, t)

pattern009

Conclusion

  • 重構遇到 identity() 時,通常都會有更簡單的 function 可用
  • chain() 遇到 function 時通常很難一眼認出來,但最少看到 converge(f, [t, identity]) 要想起 chain()

Reference

Ramda, pluck()
Ramda, project()
Ramda, uniq()
Ramda, sum()
Ramda, compose()
Ramda, map()
Ramda, chain()
Ramda, when()
Ramda, unless()