點燈坊

學而時習之,不亦悅乎

Ramda 之 call()

Sam Xiao's Avatar 2019-05-14

Ramda 的 call() 乍看很不起眼,但若搭配 converge() 之後,就能動態產生 Converging Function。

Version

VS Code 1.32.3
Quokka 1.0.195
Ramda 0.26.1

call()

import { call, add } from 'ramda';

console.log(call(add, 1, 2));

最簡單的 call(),第一個 argument 為 function,第二個 argument 之後為該 function 的 argument list,相當於執行 add(1, 2)

call()
(*… → a),*… → a
執行第一個 argument 的 function,並將之後 argument 當作該 function 的 argument list

(*… → a):任意 function

*…:任意 argument list

a:回傳 function 執行的結果

call000

Function

import { converge, prop } from 'ramda';

let product = {
  discount: 5000,
  price: 30000
};

// calPrice :: (Number, Number) -> Number
let calPrice = (discount, price) => price - discount - 1000;

// fn :: Number -> Number
let fn = product => calPrice(product.discount, product.price);

console.log(fn(product));

calPrice() 傳入 discountprice argument,會計算出折扣後價錢。

fn() 則傳入 product object,再呼叫 calPrice()

call003

Point-free

import { converge, prop } from 'ramda';

let product = {
  discount: 5000,
  price: 30000
};

// calPrice :: (Number, Number) -> Number
let calPrice = (discount, price) => price - discount - 1000;

// fn :: Object -> Number
let fn = converge(
  calPrice, [
    prop('discount'),
    prop('price')
  ]
);

console.log(fn(product));

首先將 fn() point-free。

11 行

// fn :: Object -> Number
let fn = converge(
  calPrice, [
    prop('discount'),
    prop('price')
  ]
);

console.log(fn(product));

fn() 的 argument 為 product object,包含 discountprice property,分別傳入 折扣價錢

最後要執行的是 calPrice(),分別有 discountprice 兩個 argument。

由於傳入 fn() 的是 object,因此勢必要先使用 prop() 拆解後才能傳給 calPrice() 執行。

這正是使用 fn() 時機,將 calPrice() 當作 converging function,將 prop() 當作 branching function。

目前就功能而言沒問題,唯 calPrice() 並非 point-free,是否有繼續優化空間呢 ?

call001

import { converge, prop, call, add, pipe, subtract, flip } from 'ramda';

let product = {
  discount: 5000,
  price: 30000
};

// calPrice :: (Number, Number) -> Number
let calPrice = pipe(
  add(1000),
  flip(subtract)
);

// fn :: Object -> Number
let fn = converge(
  call, [
    pipe(prop('discount'), calPrice),
    prop('price')
  ]
);

console.log(fn(product));

由於 calPrice() 有兩個 argument:discountprice,基於 currying 特性,可將 calPrice() 視為由 discount 產生的 function,其 argument 只剩下 price

第 8 行

// calPrice :: (Number, Number) -> Number
let calPrice = pipe(
  add(1000),
  flip(subtract)
);

calPrice() 最直覺會想用 pipe(),多減 1000 元就相當於 discount 多加 1000 元,所以先套用 add(1000)discount1000,再傳到下一個 subtract()

subtract() 第一個 argument 為 被減數,而 add(1000)減數,因此必須使用 flip()subtract() 的 signature 反轉後才能使用。

我們發現 calPrice() 已經 point-free 了。

14 行

// fn :: Object -> Number
let fn = converge(
  call, [
    pipe(prop('discount'), calPrice),
    prop('price')
  ]
);

由於 calPrice()discount 產生,因此 converge() 的 converging function 就不能再是 calPrice(),而要改用 call(),如此才會執行第一個 branching function。

第一個 branching function 先以 prop('discount') 拆解 object,再傳入 calPrice(),動態產生新的 function。

第二個 branching function 以 prop('price') 拆解 object,再傳入第一個 branching function 執行,這是因為 call() 的緣故。

call002

Conclusion

  • 當 converging function 並非固定,而是由其他 function 動態產生時,可搭配 call(),使其在 branching function 先動態產生 function,再由 call() 呼叫執行之

Reference

Ramda, call()
Ramda, add()
Ramda, converge()
Ramda, add()
Ramda, pipe()
Ramda, subtract()
Ramda, flip()