點燈坊

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

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

Sam Xiao's Avatar 2020-08-31

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

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。

propgte000

prop()

import { filter, pipe, prop, gte, 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(gte)(v)))

f(200)(data) // ?

第 9 行

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

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

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

propgte001

propSatisfies()

import { filter, propSatisfies, gte, 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(gte)(v), 'price'))

f(200)(data) // ?

第 9 行

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

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

propgte002

propGte()

prop()

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

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

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

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

f(200)(data) // ?

第 9 行

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

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

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

propgte005

propSatisfies()

import { filter, gte, 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 }
]

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

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

f(200)(data) // ?

第 9 行

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

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

propgte008

Wink-fp

import { filter } from 'ramda'
import { propGte } 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(propGte('price')(v))

f(200)(data) // ?

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

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

String:提供 Object 的 property

a:提供要比較的值

Object:data 為 Object

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

propgte009

Function Pipeline

import { filter, pipe } from 'ramda'
import { propGte } 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(
  propGte('price'),
  filter
)

f(200)(data) // ?

第 10 行

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

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

propgte010

Conclusion

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

Reference

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