點燈坊

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

使用 uncurryN() 使 Higher Order Function 也 Point-free

Sam Xiao's Avatar 2019-10-02

將傳入 Data 的 Function 加以 Point-free,這在 Ramda 很常見;但若 Data 傳入的目的是產生另外一個 Function,這類 Higher Order Function 可使用 ncurryN() 使其 Point-free。

Version

macOS Mojave 10.14.6
VS Code 1.38.1
Quokka 1.0.253
Ramda 0.26.1

Higher Order Function

let data = { isShipable: true };

let genShipFare = shipFare => obj => obj.isShipable ? shipFare : 0;

genShipFare(80)(data); // ?

一個非常典型的 higher order function,以 shipFare 為 argument,傳回另外一個以 obj 為 argument 的的 function。

uncurryn000

ifElse()

import { ifElse, prop, always } from 'ramda';

let data = { isShipable: true };

let genShipFare = shipFare => ifElse(
  prop('isShipable'),
  always(shipFare),
  always(0)
);

genShipFare(80)(data); // ?

關於將 data 加以 point-free,Ramda 非常在行,改用 ifElse() 之後,obj 就被 point-free 掉了。

obj 不用於產生 function,被 point-free 掉合情合理,shipFare 是用來產生 function 的重要 argument,也有可能被 point-free 掉嗎 ?

uncurryn001

pipe()

import { ifElse, prop, always, pipe, __ } from 'ramda';

let data = { isShipable: true };

let genShipFare = pipe(
  always,
  ifElse(prop('isShipable'), __, always(0))
);

genShipFare(80)(data); // ?

pipe() 是實務上最常用的 point-free 手段,若原 function 的最後一個 argument 要 pointfree,直接 pipe() 就可以;但若是其他 argument,會搭配 __()always(),其中 always() 可確保 arity 為 1

uncurryn002

uncurryN()

import { ifElse, prop, always, uncurryN, useWith, __, identity } from 'ramda';

let data = { isShipable: true };

let ifElse4 = uncurryN(4, ifElse);

let genShipFare = useWith(
  ifElse4(prop('isShipable'), __, always(0)), [
    always,
    identity
  ]
);

genShipFare(80)(data); // ?

其實回想一下 genShipFare()ifElse() 才是 main function,其他 function 只是為了 ifElse() 跑龍套用。

當要 point-free 時,首先會想到 useWith(),然後 main function 使用 ifElse()

第 5 行

let ifElse4 = uncurryN(4, ifElse);

但問題來了,ifElse() 的 arity 為 3,且回傳是 function,根本不含 data 部分,因此先使用 uncurryN(4) 將原 ifElse() 變成 4 個 argument 的 ifElse4(),為能接 data 鋪路。

ifElse4(prop('isShipable'), __, always(0)), [
  always,
  identity
]

ifElse4() 的第一個與第三個 argument 毫無懸念,但第二個 shipFare__ 取代,最後一個 argument 是 data,所以是 identity()

整體思維就是使用 uncurryN() 暴力將 ifElse()增加一個 argument,使之能搭配 useWith() 成為 point-free

uncurryn003

Conclusion

  • 要使 higher order function 也 point-free 有幾個 pattern:pipe()uncurryN()
  • pipe():若 argument 為了決定 higher order function,通常不可能是最後一個,因此會使用 __ 墊出要 point-free 的參數,再使用 always() 決定參數的 arity
  • uncurryN():動態將 main function 增加一個 argument 傳 data,再使用 useWith()identity() 則為 data 部分

Reference

Ramda, ifElse()
Ramda, prop()
Ramda, always()
Ramda, uncurryN()
Ramda, useWith()
Ramda, always()
Ramda, __()
Ramda, identity()