點燈坊

失くすものさえない今が強くなるチャンスよ

使用 pickBy() 根據自訂條件擷取 Object 部分 Property

Sam Xiao's Avatar 2019-08-02

Ramda 之 pick()pickAll() 都只能直接指定 Object 要擷取的 Property,若想要根據自訂條件選擇 Property,就只能靠 pickBy(),且可再將 pickBy() 與其他 Function 組合成新的 Function,如此可讀性更高。

Version

macOS 10.14.5
VS Code 1.36.1
Quokka 1.0.238
Ramda 0.26.1

Predicate by Value

Ramda

import { map, pickBy, includes } from 'ramda';

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let usrFn = map(pickBy(v => includes('JavaScript', v)));

console.dir(usrFn(data));

若我們不確定要擷取 object 哪個 property,而是要根據 自訂條件 來決定,此時就要使用 pickBy(),自行傳入 predicate function。

若需求是 object 的 value 含有 JavaScript 字眼,則擷取 object 的該 property。

pickBy()
((v, k) → Boolean) → {k: v} → {k: v}
根據傳入的 predicate function,回傳新的符合條件 property 的 object

((v, k) -> Boolean):自訂條件的 predicate function,注意其第一個 argument 為 value,第二個 argument 為 key

{k: v}:data 為 object

{k: v}:回傳符合條件 property 的 object

因為需求是 根據 value 決定 property,因此 pickBy() 的 predicate 只需第一個 argument 即可。

includes()
a -> [a] -> Boolean
若 value 存在於 array 內時,則回傳 true,否則回傳 false

因此將 v 傳入 includes() 第二個 argument。

pickby000

Point-free

import { map, pickBy, includes } from 'ramda';

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let usrFn = map(pickBy(includes('JavaScript')));

console.dir(usrFn(data));

includes() 只傳進第一個 argument 時,將回傳 [a] -> Boolean,正好與 map() 的 callback signature 相同,因此可以使用 includes('JavaScript') 取代 v => includes('JavaScript', v),如此就將 v 幹掉了達成 point-free。

pickBy004

import { map, pickBy, includes, compose } from 'ramda';

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let mapFn = compose(pickBy, includes);

let usrFn = map(mapFn('JavaScript'));

console.dir(usrFn(data));

map() 的 map function 由 pickBy(includes('JavaScript')) 構成,這種 f(g(x)) 的寫法,可讀性似乎不佳。

事實上這種 f(g(x)) 形式,正是典型的 compose function,最適合使用 compose() 先建立新 function。

第 9 行

let mapFn = compose(pickBy, includes);

includes()pickBy() 組合成新的 pred(),注意沒有任何 argument,因此為 point-free。

11 行

let usrFn = map(mapFn('JavaScript'));

map() 使用新的 mapFn() 並傳入 JavaScript,如此寫法可讀性甚佳,這就是 function composition 偉大之處。

pickby001

Predicate by Key

Ramda

import { map, pickBy, includes } from 'ramda';

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let usrFn = map(pickBy((v, k) => includes('it', k)));

console.dir(usrFn(data));

若需求是 object 的 key 含有 it 字眼,則擷取 object 的該 property。

由於 pickBy() 的 predicate 中,k 是在第二個 argument,因此 (v, k) 都必須使用,再將 k 傳給 includes()

pickby002

Point-free

import { map, pickBy, includes, flip, compose } from 'ramda';

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
];

let mapFn = compose(pickBy, flip, includes);

let usrFn = map(mapFn('it'));

console.dir(usrFn(data));

根據之前經驗,我們學會了組合 includes()pickBy() 成為一個新 function 當作 map() 的 map function,理論上在這裡也可如法泡製。

includes() 回傳的 signature 為 [a] -> Boolean,也就是 data 在第一個 argument,但現在需求是 key,在 pickBy() 的 predicate 是第二個 argument,因此要使用 flip() 將第一個 argument 對調成第二個 argument 的新 function,才能傳給 pickBy()

如此 map() 就能使用新的 mapFn('it'),語意非常清楚,這就是 function composition 的魔力。

pickby003

Conclusion

  • pickBy() 讓我們可以自己傳入 predicate function,遠比 pick()pickAll() 更有彈性
  • 可將 map() 的 map function 先 compose() 成新 function,如此可讀性更高,若 signature 不從人願,可再使用 flip() 加以轉換

Reference

Ramda, map()
Ramda, pickBy()
Ramda, includes()
Ramda, compose()
Ramda, flip()