點燈坊

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

使用 anyPass() 組合符合部分條件的 Predicate

Sam Xiao's Avatar 2020-02-27

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

Version

macOS Mojave 10.14.5
VS Code 1.33.0
Quokka 1.0.205
Ramda 0.26.1

Imperative

let x = undefined
let y = null
let z = ''

let f = x => {
  if (x === undefined || x === null || x === '')
    return true
  else
    return false
}

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

若要判斷 undefinednull 或 empty string,ECMAScript 可使用 if 一一判斷並搭配 ||

anypass000

Ramda

import { isNil, isEmpty } from 'ramda'

let x = undefined
let y = null
let z = ''

let f = x => isNil(x) || isEmpty(x)
    
f(x) // ?
f(y) // ?
f(z) // ?

Ramda 提供了 isNill() 判斷 undefinednullisEmpty() 判斷 empty string,也可搭配 || 判斷。

anypass001

anyPass()

import { isNil, isEmpty, anyPass } from 'ramda'

let x = undefined
let y = null
let z = ''

let f = anyPass([isNil, isEmpty])

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

更好的方式是改用 anyPass() 取代 ||,如此在語義上可明確看出 isNil()isEmpy() 只要 anyPass() 即可。

anyPass()
[(*… → Boolean)] → (*… → Boolean)
將眾多 predicate 組合成單一 predicate,只要任何 predicate 成立即可

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

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

anypass002

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.price === 200)
      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 }
]

只要是 price100200 都屬於合理價錢。

第 7 行

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

  for (let x of arr)
    if (x.price === 100 || x.price === 200)
      result.push(x)
      
  return result
}

Imperative 會先建立 result empty array,使用 for loop 對 data 一筆一筆處理,然後使用 if 判斷 price 是否為 100200,最後 push 進 result array 回傳。

anypass005

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.price === 200) ? 
  [...a, x] : 
  a
), [])
  
f(data) // ?

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

anypass007

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.price === 200)
  
f(data) // ?

ECMAScript 可使用自帶的 filter(),只要在 predicate 判斷 price100200 即可。

anypass006

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.price === 200)
  
f(data) // ?

也可使用 Ramda 的 filter() 使 f() 能 point-free。

anypass003

import { anyPass, propEq, 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(anyPass([
  propEq('price', 100),
  propEq('price', 200)
]))

f(data) // ?

以 FP 角度,我們必須提供兩個 predicate,但只要其中一個 predicate 成立即可,因此使用 Ramda 的 anyPass() 將兩個使用 propEq() 產生的 predicate 組合成單一 predicate。

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

anypass004

Conclusion

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

Reference

Ramda, any()
Ramda, anyPass()
Ramda, propEq()