練習使用 chain() 與 assoc()

若想對 Object 新增 Property,Ramda 提供了 assoc()

Version


WebStorm 2018.3.3
Quokka 1.0.136
Ramda 0.26.1

根據固定值建立新 Property


Imperative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = data => {
let result = [];
for(let x of data)
result.push({ ...x, price: 200 });

return result;
};

console.dir(getBooks(data));

Imperative 會使用 for loop,先建立新的 result array,使用 ... spread operator 保留原本 array,並加上新的 price property 與其值 200,最後 push 進 result 回傳。

assoc004

Array.prototype

1
2
3
4
5
6
7
8
const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = data => data.map(x => ({ ...x, price: 200 }));
console.dir(getBooks(data));

ECMAScript 的 Array.prototype 也內建 map(),可直接使用 Arrow Function 傳入 callback。

一樣使用 ... spread operator 保留原本 array,並加上新的 price property 與其值 200

因為 map() 因為是 data array 的 method,因此 getBooks() 一定要傳入 data,且 callback 也還有 x 參數,所以並不是 Point-free。

assoc000

Ramda

1
2
3
4
5
6
7
8
9
10
import { map, assoc } from 'ramda';

const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = map(assoc('price', 200));
console.log(getBooks(data));

若 property 是固定值,則使用 Ramda 的 assoc() 相當簡單直覺。

assoc()
string -> a -> {k: v} -> {k: v}
複製原本 object 的 property 外,還可新增 property

string : 新 propert 的 key

a:新 property 的 value

{k: v}:data 為原 object

{k: v} :回傳的新 object

assoc001

根據其他 Property 建立新 Property


Imperative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = data => {
let result = [];
for(let x of data)
result.push({ ...x, price: x.id });

return result;
};

console.dir(getBooks(data));

Imperative 會使用 for loop,先建立新的 result array,使用 ... spread operator 保留原本 array ,並加上新的 price property ,且其值為原本的 id,最後 push 進 result 回傳。

assoc005

Array.prototype

1
2
3
4
5
6
7
8
const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = data => data.map(x => ({ ...x, price: x.id }));
console.dir(getBooks(data));

ECMAScript 的 Array.prototype 也內建 map(),可直接使用 Arrow Function 傳入 callback。

一樣使用 ... spread operator 保留原本 array,並加上新的 price property 且其值為原本的 id

因為 map() 因為是 data array 的 method,因此 getBooks() 一定要傳入 data,且 callback 也還有 x 參數,所以並不是 Point-free。

assoc002

Ramda

1
2
3
4
5
6
7
8
9
10
import { assoc, chain, map, prop } from 'ramda';

const data = [
{ id: 1, title: 'Functional Programming in JavaScript' },
{ id: 2, title: 'RxJS in Action' },
{ id: 3, title: 'Speaking JavaScript' },
];

const getBooks = map(chain(assoc('price'), prop('id')));
console.dir(getBooks(data));

若要使用 assoc() 的值為既有 property,在 Ramda 則沒這麼單純。

由於 FP 強調 一次只做一件事,因此 price: x.id 在 FP 其實包含了幾件事情:

  1. 使用 prop() 讀取 x.id
  2. 使用 assoc() 新增 price property

assoc() 又需要 prop() 之後的結果,這符合 chain(f, g)(x) === f(g(x), x) 格式,因此可使用 chian()assoc()prop() 結果串起來。

chain()

Chain m => (a → m b) → m a → m b
對 array 或 function 先執行 map() ,再將結果結合起來

mChain 型別,其定義可參考 Fantacy Land,先簡單想成可以是 array 或 function 即可。

(a -> m b):第一個參數為 function,由任意型別轉成 array 或 function

m a:第二個參數為 array 或 function

m b:回傳為另一個 array 或 function

assoc003

Conclusion


  • assoc() 只要配合固定值,則用起來很簡單
  • assoc() 要配合其他 property,則要配合 chain()prop()

Reference


Ramda, map
Ramda, chain()
Ramda, assoc()
Ramda, prop()