點燈坊

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

使用 both() 實現 AND Combinator

Sam Xiao's Avatar 2020-08-29

若第一個 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 一一判斷並搭配 &&

both000

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(),再搭配 && 判斷。

both001

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。

both008

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 結果

both002

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 大於 100title 包含 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 大於 100title 包含 JavaScript 則 push 進 result,最後回傳 result array。

both003

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() 並傳入判斷 pricetitle 的 predicate。

both004

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。

both005

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。

both006

ADT

both009

在 Ramda 文件中 both() 最後一行提到支援 Applicative Functor,且範例還用到 Maybe,這是什麼呢 ?

both010

根據 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。

both011

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。

both012

Conclusion

  • and()&& 的 value 版本;而 both()&& 的 function 版本
  • both() 在 FP 也稱為 AND ccombinator
  • 若只有兩個 predicate,則 both() 等效於 allPass()
  • both() 由於底層是 && 因此支援 truthy value 與 falsy value,不必如 ifElse() 一定要傳入 transfer function
  • both() 由於底層是 &&,因此支援 short-circuitited,也就是第二個 function 有可能不執行,因此可提高執行效率
  • both() 除了能用於 ECMAScript 原生型別外,還支援 Applicative Functor,可直接搭配 Maybe Monad 使用

Reference

Ramda, both()
Ramda, ifElse()
Sanctuary, Maybe