使用 FP 思維使用 Ramda

range() 也算實務上常用的 function,只要傳入 初始值結束值,就可建立 Array,在很多語言都有內建 range(),但可惜 ECMAScript 並沒有,我們就使用 Ramda 與 FP 思維來實作吧。

Version


WebStorm 2018.3.3
Quokka 1.0.134
Ramda 0.26.1

Imperative


1
2
3
4
5
6
const range = (start, end) => {
const size = (end - start) + 1;
return [...Array(size).keys()].map(i => i + start);
};

console.log(range(1, 3));

使用 ECMAScript 建立也很簡單,首先 endstart 相減求出 size,以此 size 建立新的 Array,再透過 keys() 回傳以 index 所建立的 array。

但我們要的是以 start 為開始,再透過 map() 產生一個新的 array 即可。

其實這樣的實作也經非常精簡,也使用了 map(), 算是有 FP 的味道了。

range000

Ramda


1
2
3
4
5
6
7
8
9
10
11
12
import { pipe, keys, map, add, curry } from 'ramda';

const getSize = curry((start, end) => () => end - start + 1);
const genArray = x => [...Array(x)];
const range = curry((start, end) => pipe(
getSize(start, end),
genArray,
keys,
map(add(start)),
)());

console.log(range(1, 3));

回想我們實作 range() 的思路:

  1. startend 獲得 size
  2. size 建立新的 array
  3. 以 array 的 index 再建立新的 array
  4. 將 array 的每個 item 加上 start

既然我們已經有了想法,就將每個步驟寫成 function,然後再 compose 成新的 function,這就是 FP 的精髓。

第 3 行

1
const getSize = curry((start, end) => () => end - start + 1);

建立 getSize(),由 startend 求得 size 回傳。

第 4 行

1
const genArray = x => [...Array(x)];

由 size 建立新的 array。

至於由 index 建立新的 array,Ramda 有提供 keys(),我們直接使用即可。

將 array 的每個 item 加上 start,也可直接使用 Ramda 的 map()

1
2
3
4
5
6
const range = curry((start, end) => pipe(
getSize(start, end),
genArray,
keys,
map(add(start)),
)());

因此 range() 就是將 getSize()getArray()keys()map() 4 個 function 加以 compose 而成。

其中前兩個 function 為自己建立,keys()map() 則使用 Ramda 的 operator。

range001

Q : 改用 Ramda 之後,行數變多了,這樣有比較好嗎 ?

FP 的精髓就是 Divide and Conquer,將問題拆成更小的問題,各自擊破後,再加以組合。

FP 也真正實踐了 SRP 單一職責原則,每個 function 都真正對應一個思路步驟。

我們的思路在 pipe() 的每一個步驟都得到對應,一目瞭然。

getSize()genArray() 這種顆粒很小的 function,將來重複使用的機會就很高。

雖然行數變多,但以上這些優點都是原本寫法所沒有的。

1
2
3
import { range } from 'ramda';

console.log(range(1, 4));

其實 Ramda 已經內建了 range(),只是它所提供的為 起始值結束值 -1

range002

Conclusion


  • Ramda 讓我們實踐 FP 的精神:Divide and Conquer 成小 function,最後再 compose 成新的 function
  • Ramda 由於 function 顆粒很小,重複使用的機會非常高
  • Ramda 讓我們可以很清楚地看到演算法的思路過程
  • Ramda 其實已經內建 range(),可直接使用

Reference


Ramda, map()
Ramda, pipe()
Ramda, keys()
Ramda, add()
Ramda, curry()
Ramda, range()