點燈坊

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

使用 propGt() 判斷 Property 是否大於某值

Sam Xiao's Avatar 2020-08-30

Ramda 有提供 propEq(),特別適合為 Object 提供 Predicate,但可惜只能用在 equals(),但若要配合 gt() 時,我們能打造類似 propEq()propGt() 嗎 ?

Version

macOS Catelina 10.15.6
Wink-fp 1.23.2

Predicate

import { filter } from 'ramda'

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

// f :: a -> [Object] -> [Object] 
let f = v => filter(x => x.price > v)

f(200)(data) // ?

第 9 行

// f :: a -> [Object] -> [Object]
let f = v => filter(x => x.price > v)

使用 filter() 找到指定 price > 200 的 Object,第一個 argument 為 predicate,因此可傳入 arrow function。

propgt000

prop()

import { pipe, filter, prop, gt, flip } from 'ramda'

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

// f :: a -> [Object] -> [Object] 
let f = v => filter(pipe(prop('price'), flip(gt)(v)))

f(200)(data) // ?

第 9 行

// f :: a -> [Object] -> [Object] 
let f = v => filter(pipe(prop('price'), flip(gt)(v)))

對於這種 Object Array,且 predicate 非 等於,要使 filter() 的 callback 能 point-free 有兩種方法,先談較直覺的 prop()

由於是 Object,直覺要先使用 prop() 取得 value,再使用 gt() 比較其值,因此採用 pipe()prop()gt() 組合產生 filter() 所需的 callback。

propgt002

propSatisfies()

import { filter, propSatisfies, gt, flip } from 'ramda'

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

// f :: a -> [Object] -> [Object] 
let f = v => filter(propSatisfies(flip(gt)(v), 'price'))

f(200)(data) // ?

第 9 行

// f :: a -> [Object] -> [Object] 
let f = v => filter(propSatisfies(flip(gt)(v), 'price'))

第二個方法是採用 propSatisfies(),只要不能採用 propEq(),都可改用 propSatisfies() 達成。

propgt003

propGt()

prop()

import { filter, prop, gt, flip, pipe, useWith } from 'ramda'

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

// propGt :: String -> a -> Object -> Boolean
let propGt = useWith(
  pipe, [prop, flip(gt)]
)

// f :: a -> [Object] -> [Object]
let f = v => filter(propGt('price')(v))

f(200)(data) // ?

第 9 行

// propGt :: String -> a -> Object -> Boolean
let propGt = useWith(
  pipe, [prop, flip(gt)]
)

若要模仿 propEq() 做出 propGt(),也有兩種方式,先看叫直覺的 prop()

由於 argument 先提供 property,再提供 value,最後使用 pipe() 串起來,這剛好是 useWith() 格式。

propgt006

propSatisfies()

import { filter, gt, propSatisfies, flip, useWith, identity } from 'ramda'

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

// propGt :: String -> a -> Object -> Boolean
let propGt = useWith(
  flip(propSatisfies), [identity, flip(gt)]
)

// f :: a -> [Object] -> [Object]
let f = v => filter(propGt('price')(v))

f(200)(data) // ?

第 9 行

// propGt :: String -> a -> Object -> Boolean
let propGt = useWith(
  flip(propSatisfies), [identity, flip(gt)]
)

也可以使用 propSatisfies(),但因為其 argument 順序剛好與 propGt() 相反,因此先透過 flip() 翻轉。

propgt009

Wink-fp

import { filter } from 'ramda'
import { propGt } from 'wink-fp'

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

// f :: a -> [Object] -> [Object]
let f = v => filter(propGt('price')(v))

f(200)(data) // ?

Wink-fp 已經內建 propGt() 可直接使用。

propGt()
String -> a -> Object -> Boolean

String:提供 Object 的 property

a:提供要比較的值

Object:data 為 Object

Boolean:若成立回傳 true,否則回傳 false

propgt010

Function Pipeline

import { pipe, filter } from 'ramda'
import { propGt } from 'wink-fp'

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

// f :: a -> [Object] -> [Object]
let f = pipe(
  propGt('price'), 
  filter
)

f(200)(data) // ?

10 行

// f :: a -> [Object] -> [Object]
let f = pipe(
  propGt('price'), 
  filter
)

有了 propGt() 除了語意更清楚外,f() 也能輕鬆加以 point-free。

propgt001

Conclusion

  • propGt() 在實務上經常使用,但可惜 Ramda 沒有內建,只好自行組合並收錄在 Wink-fp
  • propGt() 可使用 prop() 組合 gt(),也可使用 propStatisfies(),Ramda 常常同一個需求有多種組合方式,就類似相同數學問題,常會有多種不同解法
  • propGte()propLte()propLt() 也可使用本方法加以打造,請舉一反三

Reference

Ramda, filter()
Ramda, prop()
Ramda, gt()
Ramda, propSatisfies()
Ramda, useWith()