若第一個 Function 結果為 Falsy Value 則回傳第一個 Function,否則回傳第二個 Function,在 FP 稱為 AND Combinator,可使用 Ramda 的 both()
實現。
Version
macOS Catalina 10.15.6
Ramda 0.27.1
Imperative
let f = n => {
if (n > 10 && (n % 2 === 0)) return true
else return false
}
f(8) // ?
f(20) // ?
f(7) // ?
若要判斷 大於 10
且為 偶數
,ECMAScript 可使用 if
一一判斷並搭配 &&
。
Extract Function
let isGt10 = n => n > 10
let isEven = n => !(n % 2)
let f = n => isGt10(n) && isEven(n)
f(8) // ?
f(20) // ?
f(7) // ?
也可將判斷 expression 抽成 isGt10()
與 isEven()
,再搭配 &&
判斷。
both()
let isGt10 = n => n > 10
let isEven = n => !(n % 2)
let both = f1 => f2 => n => f1(n) && f2(n)
let f = both(isGt10)(isEven)
f(8) // ?
f(20) // ?
f(7) // ?
可自行以 &&
實作 both()
回傳適當 function。
Ramda
import { both, gt, flip } from 'ramda'
import { isEven } from 'wink-fp'
let isGt10 = flip(gt)(10)
let f = both(isGt10)(isEven)
f(8) // ?
f(20) // ?
f(7) // ?
Ramda 則可使用 both()
取代 &&
。
both()
(*… → Boolean) → (*… → Boolean) → *… → Boolean
若第一個 function 結果為 falsy value 則回傳第一個 function,否則回傳第二個 function
(*… → Boolean)
:傳入第一個 function
(*… → Boolean)
:傳入第二個 function
*...
:傳入 data
Boolean
:回傳任一 function 結果
Example
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
{ title: 'Speaking JavaScript', price: 300 },
]
let f = a => {
let result = []
for (let x of a)
if (x.price >= 100 && x.title.includes('JavaScript'))
result.push(x)
return result
}
f(data) // ?
第 1 行
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
{ title: 'Speaking JavaScript', price: 300 },
]
要找出所有 price
大於 100
且 title
包含 JavaScript
的資料。
第 7 行
let f = a => {
let result = []
for (let x of a)
if (x.price >= 100 && x.title.includes('JavaScript'))
result.push(x)
return result
}
Imperative 會先建立 result
empty array,使用 for
loop 對 data 一筆一筆處理,若 price
大於 100
且 title
包含 JavaScript
則 push 進 result
,最後回傳 result
array。
Array.prototype.filter()
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
{ title: 'Speaking JavaScript', price: 300 },
]
let f = a => a.filter(x => x.price >= 100 && x.title.includes('JavaScript'))
f(data) // ?
也可使用 ECMAScript 的 filter()
並傳入判斷 price
與 title
的 predicate。
Ramda
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 = filter(x => x.price >= 100 && x.title.includes('JavaScript'))
f(data) // ?
也可使用 Ramda 的 filter()
使 f()
point-free。
Point-free
import { filter, both } from 'ramda'
import { propGte, propIncludes } from 'wink-fp'
let data = [
{ title: 'FP in JavaScript', price: 100 },
{ title: 'RxJS in Action', price: 200 },
{ title: 'Speaking JavaScript', price: 300 },
]
let f = filter(both(
propGte('price', 100),
propIncludes('title', 'JavaScript')
))
f(data) // ?
以 FP 角度,我們必須提供兩個 predicate,且兩個 predicate 都要成立,因此使用 Ramda 的 both()
將兩個 predicate 組合成單一 predicate。
注意新 predicate 接受的 data 與原本 predicate 都相同,一樣是 object。
ADT
在 Ramda 文件中 both()
最後一行提到支援 Applicative Functor,且範例還用到 Maybe,這是什麼呢 ?
根據 fantasy-land 定義,Applicative 必須支援 Apply 與 Functor 所有特性,而 Monad 亦需支援 Applicative 所有特性,both()
既然支援 Applicative,這表示 both()
亦支援 Monad。
Maybe Monad
import { both } from 'ramda'
import { create, env } from 'sanctuary'
let { Just } = create ({ checkTypes: false, env })
both(Just(false), Just(20)) // ?
both(Just(true), Just(20)) // ?
both(Just(10), Just(20)) // ?
both()
可直接比較 Just 內的值,若第一個 Just 內為 false
則直接回傳 Just,若為 true
則回傳第二個 Just。
import { both } from 'ramda'
import { create, env } from 'sanctuary'
let { Just, Nothing } = create ({ checkTypes: false, env })
both(Nothing, Just(20)) // ?
both(Just(20), Nothing) // ?
若其中之一為 Nothing 則直接回傳 Nothing。
Conclusion
and()
為&&
的 value 版本;而both()
為&&
的 function 版本both()
在 FP 也稱為 AND ccombinator- 若只有兩個 predicate,則
both()
等效於allPass()
both()
由於底層是&&
因此支援 truthy value 與 falsy value,不必如ifElse()
一定要傳入 transfer functionboth()
由於底層是&&
,因此支援 short-circuitited,也就是第二個 function 有可能不執行,因此可提高執行效率both()
除了能用於 ECMAScript 原生型別外,還支援 Applicative Functor,可直接搭配 Maybe Monad 使用