點燈坊

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

使用 reject() 排除 Array 中符合條件 Element

Sam Xiao's Avatar 2020-03-16

filter() 是 FP 代表性的 Function,但若想表達的是 請將符合條件的資料不顯示,當然也可以繼續使用 filter() 搭配 反向邏輯,語義則變成 請將不符合條件資料顯示;Ramda 特別提供了 reject(),讓我們能夠使用 正向邏輯 表示。

Version

macOS Catalina 10.15.3
VS Code 1.43.0
Quokka 1.0.282
Ramda 0.27.0

Imperative

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

let reject = f => arr => {
  let result = []

  for(let x of arr) {
    if (!f(x))
      result.push(x)
  }

  return result
}

let f = v => arr => reject(x => x.price === v)(arr)

f(100)(data) // ?

需求為排除 price100 的資料,reject() 當然也可使用 imperative 實作。

reject002

ECMAScript

reduce()

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

let reject = f => arr => arr.reduce((a, x) => !f(x) ? [...a, x] : a, [])

let f = v => arr => reject(x => x.price === v)(arr)

f(100)(data) // ?

reject() 最終為單一 array,故可用 reduce() 實現。

reject004

filter()

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

let reject = f => arr => arr.filter(x => !f(x))

let f = v => arr => reject(x => x.price === v)(arr)

f(100)(data) // ?

ECMAScript 並沒有內建 reject(),但可有 filter() 模擬。

reject005

Function Composition

import { filter, compose, complement, identity } from 'ramda'

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

let reject = compose(filter, complement, identity)

let f = v => arr => reject(x => x.price === v)(data)

f(100)(data) // ?

既然 reject() 能用 filter() 模擬,就能透過 identity()complement()filter() 組合出 reject()

reject006

Ramda

import { reject } from 'ramda'

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

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

f(100)(data) // ?

Ramda 已經內建 reject() 可直接使用。

reject()
(a → Boolean) → [a] → [a]
將符合條件的資料去除,並回傳新的 array

(a -> Boolean):去除條件的 predicate

[a]:data 為 array

[a]:回傳去除資料後的新 array

reject000

Point-free

propEq()

import { reject, propEq } from 'ramda'

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

let f = v => reject(propEq('price', v))

f(100)(data) // ?

reject() 的 predicate 可由 propEq() 產生使 predicate 能 point-free。

reject001

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

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

let f = compose(reject, propEq('price'))

f(100)(data) // ?

也可使用 compose() 組合 reject()propEq() 使 f() 能 point-free。

reject003

where()

import { reject, where, equals } from 'ramda'

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

let f = v => reject(where({
  price: equals(v)
}))

f(100)(data) // ?

reject() 的 predicate 另外一種用法是搭配 where(),尤其對 data 是 array of object 時非常好用。

reject007

import { reject, where, equals, objOf, compose } from 'ramda'

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

let f = compose(reject, where, objOf('price'), equals)

f(100)(data) // ?

若要使 f() 也 point-free,可使用 compose() 從內層拆起,組合 filter()where()objOf()eqauls()

reject008

Conclusion

  • 寫程式盡量避免 反向邏輯,因為人類思考較熟悉 正向邏輯,若需求為符合條件從 array 去除,則應該使用 reject() + 正向邏輯,而非 filter() + 反向邏輯
  • reject() 雖然用起來很直覺,也可使用 imperative 與 reduce() 實作,事實上 imperative 要重構時,與自行實作 reject() 思考過程類似
  • 當要使用 compose() 時,只要從內側一步一步向外拆解 function,就能順利寫出 compose()

Reference

Ramda, filter()
Ramda, reject()
Ramda, propEq()
Ramda, compose()
Ramda, complement()
Ramda, identity()
Ramda, where()
Ramda, equals()
Ramda, objOf()