點燈坊

學而時習之,不亦悅乎

使用 chain() 取代 filter() + map()

Sam Xiao's Avatar 2019-08-07

filter()map() 都是 FP 代表性的 Higher Order Function,但也可使用 chain() 實踐。

Version

macOS Mojave 10.14.5
VS Code 1.35.0
Quokka 1.0.224
ECMAScript 2019
Ramda 0.26.1

map()

import { map } from 'ramda';

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

let usrFn = map(x => {
  if (x.price < 300)
    return `${x.title} is not expensive`;
});

console.dir(usrFn(data));

若我們希望在 map() 的 map function 內加上條件,若 price 小於 300,就顯示 is not expensive

由於 map() 的特性是原來 data 有幾筆,結果有幾筆,因此 price 大於等於 300 時,會出現 undefined,這應該不是我們樂見的。

filter000

filter() + map()

import { pipe, map, filter } from 'ramda';

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

let usrFn = pipe(
  filter(x => x.price < 300),
  map(x => `${x.title} is not expensive`)
);  

console.dir(usrFn(data));

看到在 map() 的 callback 有 if,我們會重構成先使用 filter() 過濾後再去 map(),這種寫法就不會有 undefined 了。

filter001

Point-free

import { pipe, map, filter, prop, gt, concat, __ } from 'ramda';

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

let filterFn = pipe(
  prop('price'), 
  gt(300)
);

let mapFn = pipe(
  prop('title'), 
  concat(__, ' is not expensive')
);

let usrFn = pipe(
  filter(filterFn),
  map(mapFn)
);  

console.dir(usrFn(data));

也可以將 filter()map() 內的 callback 加以 Point-free 成獨立 function,但這並不是重點,可依可讀性決定要不要真的去 Point-free。

Point-free 的目的就是讓可讀性變高,但有時條件太複雜,反而會使 callback 更複雜,因此要視情況使用

filter003

chain()

import { chain } from 'ramda';

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

let usrFn = chain(x => {
    if (x.price < 300)
      return [`${x.title} is not expensive`];
    else
      return [];
  }
);  

console.dir(usrFn(data));

map() 改成 chain(),且維持原本 map function 大架構不變。

但 return string 改成 return array of string

原本沒有 else 改成 return []

因為 chain() 就是 flatMap(),因此 array of string 會變成 string,且 [] 會自動過濾,因此不會出現 undefined

filter002

Conclusion

  • chain() 會維持原本 map function 的大架構,只要稍微重構即可
  • filter() + map() 改寫幅度較大,觀念也不太一樣
  • chain() 的寫法是否有比filter() + map() 好比較見仁見智,但也不失為漂亮的寫法

Reference

Ran Bar-Zip, Using flatMap in ES2019
Ramda, chain()
Ramda, map()
Ramda, filter()
Ramda, pipe()
Ramda, prop()
Ramda, gt()
Ramda, concat()
Ramda, __()