map, filter を object にも使う方法【fp-ts の紹介】

## 結論

 `map`, `filter` を object にも使う方法の一つとして、[fp-ts](https://github.com/gcanti/fp-ts) というライブラリを利用する という方法があります。
fp-ts の `map`, `filter` は基本的には `pipe`関数と共に用います。[^1]
[^1]: `flow`関数も頻繁に使いますが、本稿では割愛します。
以下は `filter`, `map` を object に使うシンプルな例です。

```typescript:.ts
import { pipe } from "fp-ts/function"
import { array as A, record as R } from "fp-ts"

// utils
const isEven = (n: number) => n % 2 === 0
const double = (n: number) => n * 2

// data
let data1 = [1, 2, 3, 4, 5, 6]
let data2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }

//                   data1.   filter(isEven).   map(double)  // ES6
const result1 = pipe(data1, A.filter(isEven), A.map(double)) // fp-ts Array
const result2 = pipe(data2, R.filter(isEven), R.map(double)) // fp-ts Record ← filter, map を object に使う例

console.log(result1) // [ 4, 8, 12 ]
console.log(result2) // { b: 4, d: 8, f: 12 }

```


## 対象読者
- Array の `map`, `filter` を Object にも使いたいと思ったことがある方
- 解説は JavaScript で行うため TypeScript の知識は不要です
- 関数型プログラミングの知識は不要です

## 解説

「結論」に記載した以下のコードについて解説します。

```javascript
const result2 = pipe(data2, R.filter(isEven), R.map(double))
```

### 1. メソッドチェーンで書いた場合
下記の単純なコードを fp-ts で書き直す流れで説明していきます。

```javascript
// utils
const isEven = (n) => n % 2 === 0
const double = (n) => n * 2

// data
let data = [1, 2, 3, 4, 5, 6]
let result

// ES6
result = data.filter(isEven).map(double) // 1. メソッドチェーン
console.log(result) // [ 4, 8, 12 ]

```
### 2. パイプラインで書いた場合

`data.filter(isEven)` では `Array.prototype.filter`関数内の this を `data`、引数を 関数`isEven` として実行します。
まず、`filter` と `map` を this に依存するメソッドではなく、引数のみに依存する関数として定義します。

```javascript
//    filter = (fn) => (thisArg) => thisArg.filter(fn)                       // Array.prototype.filter を メソッド として利用
const filter = (fn) => (thisArg) => Array.prototype.filter.call(thisArg, fn) // Array.prototype.filter を 関数 として利用
const map = (fn) => (thisArg) => Array.prototype.map.call(thisArg, fn)       // filter, map は 『引数：fn(関数), 戻り値：「引数：thisArg(配列), 戻り値：配列 の関数」』の関数
```
上記 `filter` と `map` は関数を返す関数（高階関数）です。
const filter = **(fn ,  thisArg)** => Array.prototype.filter.call(thisArg, fn)
のように引数を２つ持つ関数として記載しないのは パイプラインによる処理 を可能にするためです。（後述します。）
なお、
const filter = **(fn ,  thisArg)** => Array.prototype.filter.call(thisArg, fn)
を
const filter = **(fn)=>(thisArg)** => Array.prototype.filter.call(thisArg, fn)
のように記載することを カリー化 と言います。[^2]
[^2]: ここでは“手動”でカリー化していますが、“自動”でカリー化する関数（[curry](https://ramdajs.com/docs/#curry)）を提供している有名な関数型プログラミングライブラリ（Ramda）があります。（もっとも、TypeScriptでは型に難があり筆者はcurryはほぼ使いません。）
関数を返す関数（高階関数）である filter と map に それぞれ、 関数`isEven` と 関数`double` を渡すことで、
配列を受け取って、配列を返す関数（下記 `filterIsEven`, `mapDouble`）を作成します。

```javascript
const filterIsEven = filter(isEven) // filterIsEven は「引数：配列, 戻り値: 配列」の関数
const mapDouble = map(double)       // mapDouble    は「引数：配列, 戻り値: 配列」の関数
```
`filterIsEven`, `mapDouble`は**一つ**の引数を受け取って**一つ**の値を返す形の関数なので、後述するようにパイプライン処理ができるようになります。
（本稿では 後述の `pipe`関数 を使った処理を「パイプライン処理」と呼びます。）
以下のように、`filterIsEven`, `mapDouble`を使って、結果（[ 4, 8, 12 ]）を得ることができます。

```javascript
const filtered = filterIsEven(data)
const result = mapDouble(filtered)
console.log(result) // [ 4, 8, 12 ]
```

関数呼び出しをネストすることで、使う変数を減らす書き方をすると、下記のようになります。

```javascript
result = mapDouble(filterIsEven(data))
console.log(result) // [ 4, 8, 12 ]
```

しかし、上記のように記載すると、関数（ここでは、`filterIsEven`, `mapDouble`など）が増えた場合にネストが深くなり少し読みずらく（書きずらく）なります。
そこで、`pipe`関数 を使うと、下記のように記載できます。

```javascript:.mjs
import { pipe } from "fp-ts/lib/function.js"

result = pipe(data, filterIsEven, mapDouble)
console.log(result) // [ 4, 8, 12 ]

// fp-ts の pipe を簡略化する場合（引数を３つに限定する場合）、
// const pipe = (a, ab, bc) => bc(ab(a)) と記載できます。
// ※ a はデータ、ab, bc は引数が１つの関数
```
fp-ts の `pipe`関数[^3] は 第一引数 に処理対象となるデータを取り、第二引数以降は、『引数を**一つ**のみ受け取り何らかの値を返す関数』を取ります。そのため、`filterIsEven`, `mapDouble` を引数が一つの関数になるように作りました。
[^3]: 同じ `pipe` という名前の関数でも、別のライブラリ（例えば ramda）だと機能が異なっている場合があります。

`filterIsEven`, `mapDouble` を 一つ前の状態 （`isEven`, `double` をそれぞれ `filter`, `map` に渡す前の状態） に戻すと下記のようになります。

```javascript 
//            data. filter(isEven). map(double)  // 1. メソッドチェーン (前掲したもの)
result = pipe(data, filter(isEven), map(double)) // 2. パイプライン
console.log(result) // [ 4, 8, 12 ]
```
こうして並べてみると、メソッドチェーン と パイプライン は書き方が似ています。
メソッドチェーン は this（=`data`）が、
パイプラインでは `data`自体 が
左から右に順に（パイプを通るかのように?）評価されます。
なお、pipe [(function.ts の最下部あたり)](https://github.com/gcanti/fp-ts/blob/master/src/function.ts) は引数が 1個でも、20個でも良いように実装されています。

### 3. fp-ts を使って書いた場合

Array用に実装された関数(高階関数)である`filter`,`map`を使用します。


```javascript 
import  * as A  from "fp-ts/lib/Array.js" //追加

//            data.   filter(isEven).   map(double)  // 1. メソッドチェーン
//       pipe(data,   filter(isEven),   map(double)) // 2. パイプライン (filter, map は Array.prototype 由来)
result = pipe(data, A.filter(isEven), A.map(double)) // 3. パイプライン (filter, map は fp-ts/lib/Array.js 由来)
console.log(result) // [ 4, 8, 12 ]
```

### 4. Object に map, filter を使う場合

（タイトル回収ですが）
Object型のデータを扱う場合、Object（Record[^4]）用に実装された高階関数である`filter`,`map` を使用します。

```javascript 
import  * as R  from "fp-ts/lib/Record.js" //追加

data = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }
//            data.   filter(isEven).   map(double)  // 1. メソッドチェーン
//       pipe(data,   filter(isEven),   map(double)) // 2. パイプライン (filter, map は Array.prototype 由来)
//       pipe(data, A.filter(isEven), A.map(double)) // 3. パイプライン (filter, map は fp-ts/lib/Array.js 由来)
result = pipe(data, R.filter(isEven), R.map(double)) // 4. パイプライン (filter, map は fp-ts/lib/Record.js 由来)
console.log(result) // { b: 4, d: 8, f: 12 }
```
[^4]: TypeScript 未学習者向けに補足すると、ここでは「Record は Object」とざっくりとした理解で良いです。（Record は Object の部分集合。）

### 5. （補足）もし Pipeline operator が使えるようになったら

[Pipeline 演算子](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Pipeline_operator) (「|>」) を JavaScript に導入しようという [提案](https://github.com/tc39/proposal-pipeline-operator) があり、これを使えるようになったら `pipe`関数 は不要になります。

```javascript 
//            data.   filter(isEven).   map(double)  // 1. メソッドチェーン
//       pipe(data,   filter(isEven),   map(double)) // 2. パイプライン
result =      data |> filter(isEven) |> map(double)  // 5. Pipeline operator
```

## fp-ts に関する補足

- `map`は（配列等の）各要素を引数に取る関数しか扱いませんが、「index (object なら key) ＋各要素」を引数に取る関数を扱える`mapWithIndex`も用意されています
- `map`と同様に、`reduce`も用意されています
- Array や Object (Record) 以外にも、Map や Set、Tuple（タプル / fp-tsでは「要素が２つの配列」を Tuple としています）など、様々なデータ型を扱う関数（or 高階関数）が用意されています
- Array や Object, Map, Set 等の既存の“構造” だけでなく、Option や Either といった関数型プログラミング言語特有の“構造”、及びそれらを扱う関数も用意されています

## Gist

https://gist.github.com/fizumi/8d542c9e2118287d9b964fd43315d889