Function 也是 Functor

map() 是 FP 最具代表性的 Higher Order Function,僅管大部分 FP 原則大家都沒搞得很懂 (包括我自己),但 map() 各位一定都用得很熟;大部分人都將 map() 用在 Array,事實上 Function as Data,map() 也能直接用在 Function 上。

Version


VS Code 1.31.1
Quokka 1.0.136
Ramda 0.26.1

Currying


1
2
3
const calPrice = discount => price => price - discount - 10;

console.log(calPrice(20)(100));

一個手動 currying 版本的 calPrice(),第一個參數為 discount,第二個參數為 price,除此之外,還在 function body 內多扣了 10 元。

這個 function 就功能而言 100% 沒問題,但能否更進一步 Point-free 呢 ?

map000

pipe()


1
2
3
4
5
6
7
8
import { pipe, subtract, flip, add } from 'ramda';

const calPrice = pipe(
add(10),
flip(subtract)
);

console.log(calPrice(20)(100));

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

subtract() 第一個參數為 被減數,而 add(10) 結果為 減數,因此必須使用 flip()subtract() 參數位置反轉後才能使用。

map001

compose()


1
2
3
4
5
6
7
8
import { compose, subtract, flip, add } from 'ramda';

const calPrice = compose(
flip(subtract),
add(10)
);

console.log(calPrice(20)(100));

既然能用 pipe() 就能使用 compose()pipe()由左到右 執行,而 compose()由右向左 執行。

map002

map()


1
2
3
4
5
6
7
8
import { map, subtract, flip, add } from 'ramda';

const calPrice = map(
flip(subtract),
add(10)
);

console.log(calPrice(20)(100));

compose() 寫法長得好像喔,只是將 compose() 換成 map(),難道 map() 等於 compose() ?

map()
Functor f => (a → b) → f a → f b
將 Functor a 轉換成 Functor b

這是 map() 的定義,注意 Ramda 從來都沒說第二個參數是 array,而是說它是個 Functor。

Array 是 Functor,function 也是 Functor,所以也能將 function 用在 map()

add(10) 回傳的是 Number -> Number,是 function。

flip(subtract)map() 的第一個參數,為 projection function,可將 add(10) 這個 function 透過 flip(subtract) 加以轉換,又因 add(10) 欠參數未填滿,所以剛好提供 calPrice 參數。

事實上在 map() 官網,最後一句話就是 Also treats functions as functors and will compose them together.,只可惜微言大意,並沒有更進一步的範例,因此很容易忽略

map003

Conclusion


  • pipe()由左向右,而 compose()由右向左,兩者可等價替換
  • 若只 compose() 兩個 function,可等價使用 map() 替換,因為 function 也是 Functor,將一個 function 透過 projection function 轉換,等效於兩個 function 去 compose

Reference


Ramda, map()

2019-03-14