Array.prototype.reduceはなぜ配列処理において万能なのか

皆さんは`Array.prototype.reduce`を使ったことがあるでしょうか。
よく`redcue`はその使い方の例として下記のように配列に格納された数値を全部足した結果を返却する処理が示されます。

```ts
// 与えられた配列の値をすべて足した値を返却する例
arr.reduce((acc, current) => acc + current, 0);
```

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

しかし、`reduce`というメソッドは非常に柔軟で汎用的な配列操作が行えるメソッドでもあります。

## reduceを利用して他のメソッドを実装してみる

`reduce`の汎用性を確かめるために、他の配列処理を`reduce`を使って再現してみようと思います。
文章量の限界もあるので全てのメソッドを記述することは出来ないため一部だけの紹介ですが、大抵の処理は`reduce`を利用することで記述可能です。

### map

`Array.prototype.map`は対象となる配列全てに関数を適用し、新しい配列を返す関数です。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

この例では`number[]`である配列`arr`の各要素を`+1`した配列を新しく生成します。

```ts
// mapでの処理
arr.map((v) => v + 1);
```

```ts
// reduceを使った処理
arr.reduce((acc, current) => {
  const newValue = current + 1;
  return [...acc, newValue];
}, []);
```

### find

`Array.prototype.find`はコールバック関数を評価して、最初に`true`を返した値自身を返す関数です。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find

この例では`number[]`の中から10以上である値を返す処理を記述します。

```ts
// findでの処理
arr.find((v) => v > 10);
```

```ts
// reduceを使った処理
arr.reduce((acc, current) => {
  if(acc !== undefined) {
    return acc;
  };

  return current > 10 ? current : undefined;
}, undefined);
```

### toReversed

`Array.prototype.toReversed`は受け取った配列の順番を反転させた配列を返すメソッドです。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed

この例では、`unknown[]`である配列`arr`を反転させた配列を返す処理を記述します。

```ts
// toReversedでの処理
arr.toReversed();
```

```ts
// reduceを使った処理
arr.reduce((acc, current) => [current, ...acc], []);
```

### reduceの汎用性

基本的には専用のメソッドを使ったほうが明示的かつ簡潔に処理を記述できるため、実務でこのような書き換えを行うことは殆どないかと思います。
しかしながら、`reduce`というメソッドの汎用性はなんとなく分かる内容になったかと思います。

## いつ使うのか

基本的には専用のメソッドを利用したほうがいいと思いますが、複数回処理をチェーンして続ける場合に`reduce`の方が簡潔に記述できる場合があります。
例として記事データが格納されている`Posts`型を下記のように処理するコードを書く場合を例にそれぞれのコードの書き方を比較してみましょう。

1. `currentUserId`と`userId`が一致するものだけを抽出
2. `publishDate`と`lastModified`はDate型に変換
3. `publishDate`を基準に並べ替えを行う

```ts
// 共通の型定義
type Posts = {
  userId: string,
  title: string,
  publishDate: string,
  lastModified?: string,
}[];
```

```ts
// reduceを使わない例
const currentUserId = 'foo';
const result = posts // Post[]型
  // `currentUserId`と`userId`が一致するものだけを抽出
  .filter((post) => post.userId === currentUserId)
  // `publishDate`と`lastModified`はDate型に変換
  .map((post) => {
    return {
      ...post,
      publishDate: new Date(post.publishDate),
      lastModified: post.lastModified ? new Date(post.lastModified) : undefind,
    };
  })
  // `publishDate`を基準に並べ替えを行う
  .toSorted((prev, next) => {
    return prev.publishDate.getTime() - next.publishDate.getTime();
  });
```

```ts
// reduceを使う例
const currentUserId = 'foo';
const result = posts // Post[]型
  .reduce((acc, current) => {
    // 必要な値の準備
    const publishDate = new Date(current.publishDate);
    const lastModified = current?.lastModified ? new Date(current.lastModified) : undefined;
    const isOld = (acc?.publishDate.getTime() ?? 0) > current.publishDate.getTime();

    // 返却する値の（構造）定義
    if (current.userId === currentUserId) {
      return acc;
    };

    return isOld ?
      [...acc, {...current, publishDate, lastModified}]:
      [{...current, publishDate, lastModified}, ...acc];
  }, []);
```

上記のコードは動かないんですが、なんとなくやりたいこととそれぞれのコードの書き方で表現した場合にどのようなコードになるのかという目安だと思ってください。

`reduce`を使わない例はそれぞれの処理が明確に別れていて、データを抽出して型を変換して、ソートするという流れが追いやすいコードになっています。
`reduce`を使ったコードの場合、はじめに必要な値を作成してその値をどのような構造で返すかという記述になっています。

個人的には前者のコードは仕様の変更が入った場合に、それぞれの処理の中で値がどのように変化しているかを意識する必要があるのに対して、後者に関しては一つの関数スコープの中で記述が収まっているため仕様の変更にも対応しやすいコードになっていると感じます。

## まとめ

普段あまり`reduce`を使わない人も、汎用性の高いメソッドであることを理解して使えるようになっておくとよりよいコードが書けるようになるかもしれません。