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) // ?
需求為排除 price
為 100
的資料,reject()
當然也可使用 imperative 實作。
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()
實現。
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()
模擬。
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()
。
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
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。
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。
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 時非常好用。
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()
。
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()