點燈坊

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

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

Sam Xiao's Avatar 2020-08-31

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

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。

proplt000

prop()

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

f(200)(data) // ?

第 9 行

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

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

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

proplt001

propSatisfies()

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

f(200)(data) // ?

第 9 行

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

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

proplt002

propLt()

prop()

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

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

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

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

f(200)(data) // ?

10 行

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

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

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

proplt005

propSatisfies()

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

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

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

f(200)(data) // ?

第 9 行

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

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

proplt008

Wink-fp

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

f(200)(data) // ?

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

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

String:提供 Object 的 property

a:提供要比較的值

Object:data 為 Object

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

proplt009

Function Pipeline

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

f(200)(data) // ?

10 行

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

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

proplt010

Conclusion

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

Reference

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