【初心者向け】map, filter, reduce で関数型プログラミングの第一歩【図あり】

# イントロ

JavaScript はオブジェクト指向言語ではあるものの、関数型プログラミング言語の影響を少なからず受けている。配列の `map`, `filter`, `reduce` メソッドがその一例である。これらはみな関数を引数に取る高階関数 (higher-order function) と呼ばれるものであり、`for` ループでよく行われる配列処理のパターンを抽象化したものとみることができる。日常的に行われる配列処理の大半は、これらのメソッドの組み合わせて簡潔に表現できる。

この記事では初心者向けにこれら3つのメソッドの動作を説明する。

**Note:** 3つのメソッドすべて、**元の配列には変更を加えず、別の新しい配列/値を返す**ことに注意されたい。

# `map`

[`map` メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map)は**各要素をある関数で変換してできる新しい配列**を返す。数学における写像 (map)に対応する。

```js
const new_arr = arr.map(f)
```

`f` は関数であり、下の図のように `arr` の各要素に対して `f` を適用し、その返り値から新しい配列を生成し、返す。要素の順番は保存される。下の図は配列の各要素を2乗する例である。

![arrmap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/3cb569f9-775d-530c-8cff-9fd3047654b3.png)

対応するコードは以下のようになる。

```js
const arr = [2, 4, 8, 3, 1, 5]

function f(x) {
  return x * x
}
const new_arr = arr.map(f)

/* arrow function バージョン */
const new_arr = arr.map(x => x * x)
```

`for` ループで表現すると以下のようになる。

```js
const arr = [2, 4, 8, 3, 1, 5]
const new_arr = []

for (const item of arr) {
  new_arr.push(item * item)
}
```


# `filter`

[`filter` メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)は**ある条件を満たす要素だけを取り出して新しい配列を返す**。文字通りフィルターで要素を濾すメソッドである。

```js
const new_arr = arr.filter(f)
```

条件を表す関数 f を引数にとり、**`f` が `true` (もしくは [truthy](https://developer.mozilla.org/ja/docs/Glossary/Truthy)) を返すような要素だけからなる新しい配列**を返す。`f` が `true` を返せば合格、`false` を返せば不合格、という具合に各要素の合否を調べ、合格した要素だけを集めて新しい配列を作る。下の図は偶数の要素だけを取り出す例である。

![arrfilter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/e0a6a39a-c2f5-7902-6c69-2f5f0086c4c8.png)

対応するコードは以下のようになる。

```js
const arr = [2, 1, 4, 6, 3, 8]

function f(x) {
  return x % 2 === 0  // 2で割り切れるかどうか
}
const new_arr = arr.filter(f)

/* arrow function バージョン */
const new_arr = arr.filter(x => x % 2 === 0)
```

`for` ループで表現すると以下のようになる。

```js
const arr = [2, 4, 8, 3, 1, 5]
const new_arr = []

for (const item of arr) {
  if (item % 2 === 0) {
    new_arr.push(item)
  }
}
```


# `reduce`

このメソッドは他2つと比べると若干複雑だが、非常に便利な関数である。

[`reduce` メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)は**配列を左から見ていって最終的に1つの値を得る**。複数の値を含む配列を1つの値へと減らすため、reduce と呼ばれる。accumulate や fold とも呼ばれる。

```js
const result = arr.reduce(f, ini)
```

例でないとわかりにくいので、和を求める例で説明する。私たち人間が暗算で配列の和を求めようと思えば、普通左から見ていって繰り返し足し算を行うだろう。例えば `[4, 1, 2, 3]` という配列ならば、初期値は0で、
<font color="Red">0</font>と<font color="Blue">4</font>を足して<font color="Lime">4</font>、
<font color="Red">4</font>と<font color="Blue">1</font>を足して<font color="Lime">5</font>、
<font color="Red">5</font>と<font color="Blue">2</font>を足して<font color="Lime">7</font>、
<font color="Red">7</font>と<font color="Blue">3</font>を足して<font color="Lime">10</font>、
と計算する。この処理は、**「<font color="Red">途中結果</font>と<font color="Blue">要素</font>を受け取って<font color="Lime">次の途中結果</font>を得る関数」を繰り返し適用することで表現できる**、ということがわかる。**この関数が `f` にあたる**。

`f` の第1引数が<font color="Red">途中結果</font>、第2引数が<font color="Blue">現在の要素</font>である。今の場合は `f` は単に2つの値を足す関数、つまり `function (x, y) { return x + y }` である。`ini` は初期値にあたり、和を求める場合は当然 0 だが、積を求めたい場合は 1 となる。図で表すと下のようになる。

![arrreduce.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/924e5227-8f11-8881-4c18-51d7ed085253.png)

これに対応するコードは以下である。

```js
const arr = [4, 1, 2, 3]

function f(x, y) {
  return x + y
}
const sum = arr.reduce(f, 0)

/* arrow function バージョン */
const sum = arr.reduce((x, y) => x + y, 0)
```

`for` ループで表現すると以下のようになる。

```js
const arr = [4, 1, 2, 3]
let sum = 0

for (const item of arr) {
  sum = sum + item
}
```

これだけ見れば、「和とか積みたいな数学的な処理にしか使えないのか」という印象を持つかもしれないが、最初に述べたとおり「配列を左からシーケンシャルに処理する」という形の処理であれば何でもできるということを覚えておきたい。その際、`f` としてどのような関数を指定すればよいかは上述の例のように考えれば分かるはずである。

例えば下のように配列をオブジェクトに変換したいとき、

```js
const users = [{ "id": "U1321", "name": "John" }, { "id": "U17583", "name": "Mary" }]
// ↓
{ "U1321": "John", "U17583": "Mary" }
```
`reduce` を使って次のようにできる。

```js
users.reduce((cur, user) => {
  cur[user.id] = user.name
  return cur
}, {})
```

# 注意点

`map` と `filter` は実行するたびに新しい配列を生成する。巨大な配列に対してこのようなメソッドのチェーンを実行すると、無駄にメモリを圧迫しガーベージコレクションを発生させる状態になりかねない。例えば、下の例では２回無駄に配列が作られることになるが、実際には新しく配列を作らずに同じ計算をすることができる。

```js
const arr = [1, 2, ..., 100000000]
const new_arr = arr
  .map(x => x * x)             // <- ここで配列が作られる
  .filter(x => x % 3 == 0)     // <- ここでも
  .reduce((x, y) => x * y, 1)
```

このようなケースでは `for` ループ1つでまとめるか、stream ベースで lazy に処理できる [RxJS](https://rxjs.dev/) や [Stream.js](https://winterbe.github.io/streamjs/) のようなライブラリを使うのがいいだろう。

# もっと例

- ユーザーの配列から、10歳以下のユーザーの名前の配列を求める

```js
users
  .filter(user => user.age <= 10)
  .map(user => user.name)
```

- ユーザーの配列から、20歳以上のユーザーの所持アイテム数の合計を求める

```js
users
  .filter(user => user.age >= 20)
  .reduce((cur, user) => cur + uesr.itemCount, 0)
```

- 非同期な(`Promise` を返す)関数の配列をシーケンシャル(逐次的)に実行する

```js
promises
  .reduce((cur, p) => cur.then(() => p()), Promise.resolve())
// ↓
// Promise.resolve().then(() => promises[0]).then(() => promises[1])...
```