寫程式免不了要使用判斷邏輯,若只有 Predicate 為 false
才執行 Function,true
回傳原值,Ramda 提供了 unless()
。
Version
macOS Catalina 10.15.4
VS Code 1.44.0
Quokka 1.0.285
Ramda 0.27.0
Imperative
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let f = a => {
let result = []
for (let x of a) {
x_ = Object.assign({}, x)
if (x.category !== 'FP')
x_.price = x.price * 0.8
result.push(x_)
}
return result
}
f(data) // ?
第 1 行
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
若 category
不為 FP
,其 price
將打 8 折。
第 7 行
let f = a => {
let result = []
for (let x of a) {
x_ = Object.assign({}, x)
if (x.category !== 'FP')
x_.price = x.price * 0.8
result.push(x_)
}
return result
}
f(data) // ?
Imperative 會先建立 result
empty array,使用 for
loop 對 data 一筆一筆處理,然後使用 Object.assign()
clone object,若 category
property 不為 FP
,則 price
打 8 折,最後 push 進 result
array 回傳。
reduce()
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let f = a => a.reduce((a, x) => (
(x.category !== 'FP') ?
[...a, {...x, price: x.price * 0.8 }] :
[...a, x]
), [])
f(data) // ?
只要能使用 for
loop,就能使用 reduce()
改寫。
map()
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let f = a => a.map(x => {
if (x.category !== 'FP')
return {...x, price: x.price * 0.8 }
else
return x
})
f(data) // ?
ECMAScript 比較好的方法是改用 map()
,並在其 callback 中使用 if else
判斷 category
是否為 FP
。
Ramda
import { map } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let f = map(x => {
if (x.category !== 'FP')
return {...x, price: x.price * 0.8 }
else
return x
})
f(data) // ?
也可使用 Ramda 的 map()
取代,可使 f()
能 point-free。
ifElse()
import { map, ifElse, propEq, assoc, identity, chain, prop, useWith, multiply } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let applyDiscount = useWith(
multiply, [identity, prop('price')]
)
let f = map(ifElse(
propEq('category', 'FP'),
identity,
chain(assoc('price'), applyDiscount(0.8))
))
f(data) // ?
Ramda 正規方式是使用 ifElse()
取代 if else
。
當 propEq()
為 true
時,使用 identity()
維持不變,否則使用 chain()
組合 assoc()
與 applyDiscount()
改變 price()
。
Lens
import { map, ifElse, propEq, identity, lensProp, over, multiply } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let priceLens = lensProp('price')
let f = map(ifElse(
propEq('category', 'FP'),
identity,
over(priceLens, multiply(0.8)),
))
f(data) // ?
若要修改 object,透過 lens 是不錯方式。
第 9 行
let priceLens = lensProp('price')
先使用 lensProp()
為 price
property 建立 lens。
13 行
over(priceLens, multiply(0.8)),
直接透過 over
修改 price
property,不必搭配 assoc()
、chain()
與 prop()
。
unless()
import { map, unless, propEq, identity, lensProp, over, multiply } from 'ramda'
let data = [
{ title: 'FP in JavaScript', price: 100, category: 'FP' },
{ title: 'Rx in Action', price: 200, category: 'FRP' },
{ title: 'Speaking JavaScript', price: 300, category: 'JS'}
]
let priceLens = lensProp('price')
let f = map(unless(
propEq('category', 'FP'),
over(priceLens, multiply(0.8))
))
f(data) // ?
與其 ifElse()
搭配 identity()
,Ramda 的提供了更精簡的 unless()
,只有當 false
時才會執行該 function,true
回傳原值。
unless()
(a → Boolean) → (a → a) → a → a
當 Predicate 為false
時執行 function,否則回傳原值
(a -> Boolean)
:predicate function,回傳 true
或 false
(a -> a)
:false
所執行 function
a
:傳入 data
a
:回傳 data
Conclusion
- 若要修改 object,改用 lens 是很不錯方式,可避免組合
prop()
與assoc()
的尷尬,直接使用over()
即可 - 當遇到
ifElse(f, identity, t)
這種 pattern 時,可考重構成unless()
Reference
Ramda, unless()
Ramda, ifElse()
Ramda, map()
Ramda, lensProp()
Ramda, identity()