點燈坊

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

使用 allPass() 組合符合所有條件的 Predicate

Sam Xiao's Avatar 2020-02-28

Ramda 有 all() 並不令人訝異,ECMAScript 也有 every(),但 Ramda 又另外提供了 allPass(),這與 all() 有什麼不同呢 ?

Version

macOS Mojave 10.14.5
VS Code 1.33.0
Quokka 1.0.205
Ramda 0.26.1

Imperative

let x = 8
let y = 20
let z = 17

let f = x => {
  if (x > 10 && (x % 2 === 0))
    return true
  else
    return false
}

f(x) // ?
f(y) // ?
f(z) // ?

若要判斷 大於 10 且為 偶數,ECMAScript 可使用 if 一一判斷並搭配 &&

allpass002

let x = 8
let y = 20
let z = 17

let isGt10 = x => x > 10
let isEven = x => !(x % 2)

let f = x => isGt10(x) && isEven(x)
    
f(x) // ?
f(y) // ?
f(z) // ?

也可將判斷 expression 抽成 isGt10()isEven(),再搭配 && 判斷。

allpass003

Ramda

import { allPass } from 'ramda'

let x = 8
let y = 20
let z = 17

let isGt10 = x => x > 10
let isEven = x => !(x % 2)

let f = allPass([isGt10, isEven])

f(x) // ?
f(y) // ?
f(z) // ?

Ramda 則可使用 allPass() 取代 &&

allPass()
[(*… → Boolean)] → (*… → Boolean)
將眾多 predicate 組合成單一 predicate,必須所有 predicate 都成立

[(*… → Boolean)]:眾多 predicate 組合在 array 中

(*… → Boolean):組合成單一 predicate

allpass004

Imperative

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

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

  for (let x of arr)
    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 = arr => {
  let result = []

  for (let x of arr)
    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。

allpass005

ECMAScript

reduce()

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

let f = arr => arr.reduce((a, x) =>
  (x.price >= 100 && x.title.includes('JavaScript')) ?
  [...a, x] :
  a
, [])
    
f(data) // ?

只要能使用 for loop,就能使用 reduce() 改寫。

allpass007

filter()

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

let f = arr => arr.filter(x => x.price >= 100 && x.title.includes('JavaScript'))
    
f(data) // ?

ECMAScript 可使用 filter() 並傳入判斷 pricetitle 的 predicate。

allpass006

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。

allpass007

import { allPass, propSatisfies, gte, __, includes, 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(allPass([
  propSatisfies(gte(__, 100), 'price'),
  propSatisfies(includes('JavaScript'), 'title')
]))

f(data) // ?

以 FP 角度,我們必須提供兩個 predicate,但必須兩個 predicate 都成立,因此使用 Ramda 的 allPass() 將兩個 predicate 組合成單一 predicate。

注意新 predicate 接受的 data 與原本 predicate 都相同,一樣是 object。

allpass008

Conclusion

  • all() 結果是 boolean;而 allPass() 結果是 predicate
  • all() 用於 array;而 allPass() 則用在組合多 predicate 成單一 predate,適合給 filter() 這類 function 使用

Reference

Ramda, allPass()
Ramda, all()
Ramda, includes()
Ramda, lte()
Ramda, propSatisfies()