【JavaScript】flat と map があるのに flatMap は必要なのか？【Monad】

JavaScriptの配列型には`flatMap`というメソッドがある。`flatMap`は同じく配列のメソッド`map`と`flat`を連続して適用したのと同じ処理を一つのメソッドで行うものである。

```javascript
[0, 10, 20].map(x => [x, x + 1]).flat(); // [0, 1, 10, 11, 20, 21]

[0, 10, 20].flatMap(x => [x, x + 1]); // [0, 1, 10, 11, 20, 21]
```

これ`flatMap`要るか？別に2回に分けて書けばよくないか？配列には他にも`reverse`や`filter`や`reduce`などたくさんのメソッドがあるのに、なぜ`flat`と`map`だけはそれらを組み合わせた`flatMap`という専用メソッドがあるのか？

それには理由がある。なぜなら`flatMap`は**関数型プログラミング**において**とても重要で特別な関数**だからである。

# Monad
`flatMap`のシグネチャーは以下のように表せる。ただし、実際の`flapMap`では`this`になる配列をここでは`input`という引数で表している。

```typescript
function flatMap(input: Array<T>, transform: T => Array<U>): Array<U>
```

この何の変哲もない関数シグネチャーは実は配列のflatMap以外のいろんな場所に現れる。

## async 関数は flatMap です
例えばPromise。上の`flatMap`のシグネチャーの`Array`を`Promise`に置き換えてみる。

```typescript
function flatMap(input: Promise<T>, transform: T => Promise<U>): Promise<U>
```

このシグネチャーはasync関数に現れている。以下のコードのasync関数`fn1`を考えてみる。

```typescript
function fn2(): Promise<number>;

async fn1(): Promise<string> {
  const n = await fn2();
  if(n === 0) throw new Error();
  return n.toString();
}
```

この`fn1`のやっている`async` `await`とは何か、それは以下のような`flatMap`なのであると言える。

```typescript
flatMap(fn2(), n => {
  if(n === 0) return Promise.reject(Error());
  return Promise.resolve(n.toString());
});
```

関数シグネチャーを確認してみてほしい。ちゃんと`flatMap`の形式になっていることがわかる。ここでは`flatMap`は`Promise`を`input`として受け取り、それがresolveされた時に`transform`を実行してその返り値を返すような関数となる（例外が投げられた場合は省いた説明）。

```typescript
function flatMap(input: Promise<T>, transform: T => Promise<U>): Promise<U> {
  return new Promise((resolve, reject) => {
    input.then(x => {
      const next = transform(x);
      next.then(y => resolve(y));
      next.catch(e => reject(e));
    });
    input.catch(e => reject(e));
  });
}
```

## オプショナルチェーン(`?.`演算子)は flatMap です
他にもオプショナルチェーン。今度は以下のようなシグネチャーを考える。これもこれまでの`flatMap`と同じ形式をしている。

```typescript
type Optional<T> = T | undefined;
function flatMap(input: Optional<T>, transform: T => Optional<U>): Optional<U>
```

以下のコードを考える。

```typescript
function fn3(): Map<string, number> | undefined;

const n: number | undefined = fn3()?.get('key');
```

このオプショナルチェーン演算子（`?.`）がやっていることは何か、これも以下のような`flatMap`なのであると言える。

```typescript
const n = flatMap(fn3(), m => m.get('key'));
```

関数シグネチャーが`flatMap`の形式になっていることを確認してほしい。ここでは`flatpMap`は`input`が`undefined`ならそのまま`undefined`を返し、`undefined`以外なら`transform`を実行してその返り値を返す関数となる。オプショナルチェーンがやっているのはこういうことだったであろう。

```typescript
function flatMap(input: Optional<T>, transform: T => Optional<U>): Optional<U> {
  if(input === undefined) return undefined;
  else return transform(input);
}
```

## 関数型言語では
このように`flatMap`のパターンは配列だけでなくいろいろな場所に現れるという事がわかる。このパターンは非常に便利で汎用的に使えるのでMonadという名前までついている。

JavaScriptではこれら様々な場所に現れるflatMapを対象の型ごとに別の構文で記述していた。つまり、配列であれば`flatMap`メソッド、`Promise`であれば`async` `await`、オプショナル型であれば`?.`という様に。しかしこれらが同じ形式の操作を行っているという事なら同じ構文で記述できるのでは？それは実際その通りで、関数型言語ではこれらを全く同じ構文で記述できたりする。例えばScalaではこれらは以下のように全て同じ形式で記述できる。

```scala
// Array flatMap
for {
  x <- Vector(0, 10, 20)
  y <- Vector(x, x + 1)
} yield y

// Async await
for {
  n <- fn21()
  _ = if (n == 0) throw new Exception() else ()
} yield n.toString

// Optional chain
for {
  x <- fn3()
  y <- x.get("key")
} yield y
```

上の`for`を使った構文はfor内包表記と呼ばれている。for内包表記は抽象操作flatMapを行うための専用構文である。つまりScalaは言語機能として組み込むほどflatMapを重要視している。

# まとめ
ということで配列の`flatMap`メソッドは単なる`flat`と`map`の組み合わせ以上の意味を持っていることが分かった。`flatMap`の操作はMonadという概念で抽象化することができる。この抽象化されたflatMapはプログラム上の様々な場所で現れ、コードをシンプルに記述するのに便利なパターンであることが分かった。それだけ重要な関数なので`flat`も`map`もあるのにあえて`flatMap`を標準に入れたんじゃないかと思っている（これはまあ想像だが）。

# 参考資料
- [なっとく！関数型プログラミング](https://www.shoeisha.co.jp/book/detail/9784798179803)
- https://youtu.be/C2w45qRc3aU?si=G4dUxB9AMamGEKa2
  - 本稿では単に同じパターンがありますねという説明しかしてないが、Monadの意味論的な説明もしているので有益です