點燈坊

學而時習之,不亦悅乎

使用 useWith() 將兩個以上 Argument 全部 Point-free

Sam Xiao's Avatar 2019-08-08

對於只有一個 Argument 的 Function,我們可以很輕易地 Point-free,對於 Arity 為 2 以上的 Function 呢 ? 此時就要使用 Ramda 殺手級 Higher Order Function:useWith()

Version

macOS Mojave 10.14.5
VS Code 1.35.0
Quokka 1.0.224
Ramda 0.26.1

Functional

import { find, propEq } from 'ramda';

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

let usrFn = price => arr => find(propEq('price', price), arr);

usrFn(100)(data); // ?

若我們想根據 price 找資料,建立了 usrFn(),其中第一個 argument 為要搜尋的 price,第二個 argument 為 arr,再使用 find()propEq() 組合。

find()
(a -> boolean) -> [a] -> a | undefined
根據 predicate function 搜尋 array 中的資料

usewith003

Point-free

import { find, propEq } from 'ramda';

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

let usrFn = price => find(propEq('price', price));

usrFn(100)(data); // ?

由於 arr 在最後一個 argument,因此可輕易 point-free 掉 arr

usewith000

Point-free

import { find, propEq, useWith, identity } from 'ramda';

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

// usrFn :: (a -> Boolean) -> [a] -> a | undefined
let usrFn = useWith(
  find, [
    propEq('price'), 
    identity
  ]
);

usrFn(100)(data); // ?

對於這種超過一個 argument 的 function,若我們想將全部 argument 都 point-free,可以使用 useWith(),他將回傳一個新的 curried function,且 argument 個數與原本 function 相同。

useWith()
((x1, x2, ...) -> z) -> [(a -> x1), (b -> x2), ...] -> (a -> b -> ... -> z)
建立一個與原 function 的 argument 個數相同的新 function,且各 argument 會先經過 transformer function 處理過再傳給原 function 執行

((x1, x2, ...) -> z):原本尚未 curried、尚未 point-free 的 function

[(a -> x1), (b -> x2), ...]:為 array,有幾個參數就會有幾個 transformer function,新 function 的所有參數,都會經過 transformer function 執行過,結果才會傳給原 function

(a -> b -> ... -> z):回傳新 function,且是 curried function

usewith002

let usrFn = price => arr => find(propEq('price', price), arr);

let usrFn = useWith(
  find, [
    propEq('price'), 
    identity
  ]
);

要將 usrFn() 重構成 point-free 很簡單:

  1. find() 放在 useWith() 的第一個 argument
  2. 將原本 在 find() 第二個 argument 的 propEq('price') 改寫在 array 內
  3. price 不變,則使用 identity()

identity()
a -> a
建立傳回原本值的 function

如此 pricearr 兩參數就被幹掉了,成了全部 point-free。

usewith001

Conclusion

  • Arity 為 2 以上的 function,可使用 useWith() 加以 point-free
  • useWith() 會使得原本 function 的 signature 較不明顯,建議馬上補上註解

Reference

Ramda, find()
Ramda, useWith()
Ramda, identity()