練習 prop() 與 flip()

同一個需求在 Ramda 常有不同的寫法,取決於你對 Operator 熟悉程度與經驗,隨著 Ramda 功力的提升,就能寫出更精簡的程式碼。

Version


WebStorm 2018.3.3
Quokka 1.0.134
Ramda 0.26.1

Imperative


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const data = [1, 2, 3];

const bookLut = {
1: 'Functional Programming in JavaScript',
2: 'RxJS in Action',
3: 'Speaking JavaScript',
};

const getBooks = data => {
let result = [];
for(let item of data) {
result.push(bookLut[item]);
}
return result;
};

const result = getBooks(data);
console.log(result);

一個實務上常見的需求,由 API 所得到的資料只有 代碼,而我們需要根據 代碼 轉成 對應資料 顯示。

至於 代碼顯示文字 的對照表,會存在 object 內。

Imperative 會使用 for loop,若在 object 比對到,則 push() 到新的 result array,最後回傳 result array。

prop000

Array.prototype


1
2
3
4
5
6
7
8
9
10
11
12
const data = [1, 2, 3];

const bookLut = {
1: 'Functional Programming in JavaScript',
2: 'RxJS in Action',
3: 'Speaking JavaScript',
};

const getBooks = data => data.map(x => bookLut[x]);

const result = getBooks(data);
console.log(result);

Array.prototype 有自帶 map(),所以我們也可以直接使用,再透過 bookLut[x] 加以對照轉換。

這種寫法已經比 Imperative 精簡很多,但仍有兩個問題:

  1. 由於內建的 map() 不是 Curried Function,因此 getBooks() 仍然要提供 data 參數,無法如 Point-Free Style 那樣精簡
  2. x => bookLut[x] 這種 callback,是否有 Point-free 寫法呢 ?

prop001

Ramda


1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { map } from 'ramda';

const data = [1, 2, 3];

const bookLut = {
1: 'Functional Programming in JavaScript',
2: 'RxJS in Action',
3: 'Speaking JavaScript',
};

const getBooks = map(x => bookLut[x]);

const result = getBooks(data);
console.log(result);

Ramda 亦提供 map(),與 Array.prototypemap() 不同在於:他是個 Curried Function,最後一個參數為 data,因此 getBooks() 可以使用 Point-free 寫法,如此將省下一個參數,程式碼更精簡。

map()
(a -> b) -> a -> b
將 a 格式資料轉換成 b 格式資料,且筆數不變

(a -> b)map() 要轉換的 function

[a]:data 為 array

[b]:回傳為新的 array

由於最後一個參數為 data,使得 map() 可使用 Point-free。

prop002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { map, prop, __ } from 'ramda';

const data = [1, 2, 3];

const bookLut = {
1: 'Functional Programming in JavaScript',
2: 'RxJS in Action',
3: 'Speaking JavaScript',
};

const getBooks = map(prop(__, bookLut));

const result = getBooks(data);
console.log(result);

不過之前的寫法,map() 的 callback 仍然不是 Point-free,Ramda 特別提供了 prop() operator,專門產生這類 callback。

Ramda 的 operator 最後一個參數都是 data,因此可以很簡單的省略 data 參數做 Point-free,但本文需求有些特別,map() 傳進來的資料並不是 prop() 最後一個參數,而是第一個參數。

對於這種特殊需求,Ramda 另外提供 __ operator 做 placeholder,專門負責傳入 data。

prop()
s -> {s: a} -> a | undefined
傳入 key 與 object,傳回 value

s:object 的 key

{s: a}:data 為 object

a | undefined:傳回 value,若找不到為 undefined

__
針對 data 不在最後一個參數的 operator 做 placeholder

prop003

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { map, prop, flip } from 'ramda';

const data = [1, 2, 3];

const bookLut = {
1: 'Functional Programming in JavaScript',
2: 'RxJS in Action',
3: 'Speaking JavaScript',
};

const getBooks = map(flip(prop)(bookLut));

const result = getBooks(data);
console.log(result);

若不想使用 __,也可以使用 flip(),將 prop 的兩個參數的順序調換,則可以使用 Point-free。

flip()
((a, b, c, ...) -> z) -> (b -> a -> c -> ... -> z)
將 function 的前兩個參數順序調換,以配合 Point-free

prop004

Conclusion


  • prop() 會傳回 Ramda 所需要的 callback,只要傳入 object 的 property key 即可
  • 決大部分 data 都在 operator 的最後一個參數,可輕鬆做 Point-free,但若剛好不是最後一個參數,可使用 __ 做 placeholder
  • 也可以使用 flip() 將參數順序調換

Reference


Ramda, map()
Ramda, prop()
Ramda, __
Ramda, flip()