モナド初学者の鬼門, Effect / IO モナドを攻略する

PureScript における`Effect`, Haskell における`IO`は, `Maybe`モナドあたりを勉強して「モナド完全に理解したわ」となった初学者[^beginner]を殺す機械として有名ですが, ヤツをぶち殺すことを目標にした記事です。

[^beginner]: まあ私も初学者なんですが。

## この記事は誰のためのものか

以下のような方に向いています。

- 参照透過な純粋関数しか扱えない PureScript や Haskell でどうやって作用を扱っているのか知りたい
- 世界すべてを引数に渡すとかいうファンタジックな説明[^real-world]を見てしまいブラウザバックしてしまった
- [モナドは箱である](https://qiita.com/suin/items/0255f0637921dcdfe83b)みたいな説明を見て「あーそーゆーことね完全に理解した」と自惚れた経験がある

また, 説明の都合上基本的な JavaScript の知識があるととっつきやすいと思います。 べつに JavaScript に拘る必要はないので,

- 関数を第一級オブジェクトとして扱えて(関数を変数に代入したり, 関数の引数や返り値に関数を使えたりできるくらいの意味)
- 配列やイテレータに`flatMap`みたいなメソッドがある

言語に親しんでいるならフィーリングで読めるかと。 たとえば Rust や Swift, ~~Python~~ なら文句ないですし, ナウな Java も関数型オブジェクトで似たようなことができて Stream API に`flatMap`があるのでいけるはずです。

**(追記)** しばらく触っていなかったのですっかり忘れていたのですが Python に`flat_map`はありませんでした。 お詫びに自分で実装したので許してください。

```python
def flat_map(f, arr):
  return sum(map(f, arr), [])
```

[^real-world]: この説明自体は理論的には正しいはずですが, まず前提として[`State s`の理解が必須](https://qiita.com/acple@github/items/779792002ba67161d37c)ですし, やっぱ普通の人は`RealWorld`なんていうのは受け入れがたいと思うので本記事のような泥臭い解釈のほうが通りがいいんじゃないかなぁと思います。
## 純粋な関数と純粋でない関数

関数には**純粋なものとそうでないもの**があります。 純粋な関数というのは簡単に言えば, 入力から出力が一意に決まり, 実行の前後で外部の環境に影響がないようなものです。

たとえば, 以下のふたつの値を足し合わせるような関数は純粋です。

```javascript
function sum(a, b) {
  return a + b;
}
```

一方で, 以下の乱数を取得するような関数は純粋ではありません。

```javascript
function rand(a, b) {
  return Math.floor(Math.random() * (b + 1 - a) + a);
}
```

純粋な関数において, 「引数がない関数」なんていうのはナンセンスです。 それは定数にしかなりえませんからね。 しかし, 純粋でない関数においてはそうではありません。 たとえば以下の関数は 6 面体のサイコロをシミュレートしますが, 引数が一切必要ないことがわかるかと思います。

```javascript
function dice6() {
  return rand(1, 6);
}
```

JavaScript では関数は第一級オブジェクトです。 普通の用途ではすぐに実行されて何らかの値が取り出されるので, これそのものを取り回すことはあまりないので意識されづらいですが,`dice6()`と`dice6`は別物で, 前者は`number`であり,後者は`number`を返す関数`()=>number`です。

## `Effect`の正体

なんでいきなりこんな話を始めたのか? それは, **これこそが`Effect`の正体だから**です。 `Effect`とは, 要するに「実行される瞬間を待っている純粋でない関数」を, ひとつの値として扱っているようなものなのです。 いかに副作用をもつ関数といえど, 実行されなきゃただの値です。

> **あなたの知らない真実**: レシピが勝手に料理を作ることはない。

JavaScript でいう`()=>number`は PureScript では`Effect Number`と表記されます。 これをこの記事では「**`number`の作用**」などと呼ぶことにしましょう。

**配列**はモナドなにそれな人にも身近なモナドの一種ですが, 以下のように好対照なのがわかります。

||配列|作用|
|:-:|:-:|:-:|
|JavaScript での型表記|`T[]`|`()=>T`|
|PureScript での型表記|`Array a`|`Effect a`|
|`T`の値を取り出す|`x[n]`|`x()`|

## 作用を引数にとる関数

さて, ここで 3 個のサイコロをふった合計――専門用語(?)で 3D6――を考えるとします。

関数を見ればすぐ実行したがるプログラマは以下のようなコードを書こうとするかもしれません。

```javascript
const result3d6 = dice6() + dice6() + dice6();
```

これはこれでいいのですが, 以下のような問題があります。

1. 使い回しができない。 3D6 を何度も振るたびに上の式を書くのは少し面倒
2. ほかのサイコロに対応していない。 同様に 3D4 や 3D20 を振る処理が別になってしまう

関数が第一級オブジェクトであることを利用すれば, 以下のような関数が書けます。

```javascript
function roll3d(d) {
  return function() {
    return d() + d() + d();
  }
}

const roll3d6 = roll3d(dice6); // サイコロの種類ごとに定義できる

const result3d6 = roll3d6(); // 使いまわして実行できる
```

この関数`roll3d`は PureScript の世界観から言えば`number`の作用を引数にとって`number`の作用を返す関数といえます。

ここで注目してほしいのは, **`roll3d`自身は「純粋な関数」である**ということです。 `roll3d`は副作用を起こしません。 だって`roll3d`が実行されて`roll3d6`に代入された時点では純粋でない関数を実行しませんからね。 副作用を起こしているのは`roll3d6`の実行時です。

ここまでで「純粋関数型言語で作用を扱う」仕組みというのが見えてきたのではないでしょうか。 実際にサイコロを振らなければ, 「サイコロを振った合計を出す」ことはできません。 しかし, **サイコロを振らなくたって「サイコロを振って合計してね」という指示を伝える**ことはできるのです。

## 配列を`flatMap`, 作用も`flatMap`

いま, サイコロを 1 個振った結果に 2 点上方修正する――専門用語(?)で 1D6 + 2――ことを考えます。 普通なら以下でよいでしょう。

```javascript
const revisedResult = dice6() + 2;
```

ですが, 純粋な計算のバージョンを考えると以下のようには書けません。

```javascript
const revisedRoll = dice6 + 2; // Oops!
```
`number`同士を足すことはできても`number`の作用と`number`を勝手に足すことはできません[^undefined-plus]。これは`number`の配列と`number`を足せない[^undefined-plus]ことに似ています。 こういうミスはみんな初学者のころにやりましたよね。

```javascript
const arr = [1, 2, 3, 4, 5, 6];

const arrPlus2 = arr + 2; // Oops!!
```

初学者のころやらかしたということは, 「はー? 俺がどうしてほしいかくらい予想つくだろバチボが」という気持ちが心のドコかにあったということです。 実際, 配列に対してやるべきことなんて決まりきってます。 決まりきっているので, `flatMap`メソッドがあります。 `flatMap`メソッドは私たちの気持ちを汲み取ってくれるのです。

```javascript
const arrPlus2 = arr.flatMap(x => [x + 2]); // [3, 4, 5, 6, 7, 8]
```
配列と作用が好対照なのは示しました。 なら, 私たちの気持ちを汲み取ってくれる**作用版の`flatMap`**を考えることはできないのでしょうか? この感情を**抽象化**といいます。

世間的には「抽象的すぎる」だの「具体的な話をしろ」だのと抽象的というのはたいてい悪いことのように扱われてしまいます。 ですが JavaScript で文字列の結合と配列の結合がどちらも`concat`メソッドという名前になっているように, **似たようなことは中身が違っても同じインタフェースでできたほうがみんな幸せです**。

![すっげぇワルの敵が使ってくるヤツじゃ～ん!!](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241027/5888d442-0304-60b8-fde1-b50521ac4f4d.png)
*図1. 「抽象化」はすっげぇワルの敵が使ってくるヤツではありません。*

JavaScript の場合プロトタイプを利用してかなり自由に既存のオブジェクトをいじれますので, 実際に作用版の`flatMap`を実装してみましょう。

型は方針の強力なヒントになります。

> 配列`T[]`の`flatMap`は`(x: T)=>R[]`な関数を引数にとって`R[]`を返します。

同様に,

> 作用`()=>T`の`flatMap`は`(x: T)=>()=>R`な関数を引数にとって`()=>R`を返すべきです。

ここで作用`()=>T`は実行すれば`T`が得られるのですから, 一度実行して得られる値を引数の関数に与えてみましょう。 そうしたならば当然`()=>R`を返しますよね。 これを返せばいいでしょうか?

```javascript
Function.prototype.flatMap = function(f) {
  return f(this()); // ()=>R
};
```

ちょっと違います。 だって, 本当に実行してしまったらその時点で純粋性は壊れてしまいます。 じゃあ実行待ちにするために全体を関数で囲んでみましょうか。

```javascript
Function.prototype.flatMap = function(f) {
  const e = this;
  return function() { // ()=>(()=>R)
    return f(e());
  }
};
```

今度は型が合いません。 先に示したとおり, 外側を実行するわけには行かないので, 内側を実行します。

```javascript
Function.prototype.flatMap = function(f) {
  const e = this;
  return function() { // ()=>R
    return f(e())();
  }
};
```

いい感じです! ぶっちゃけ, やってることはほぼほぼただの関数合成なのがわかると思います。 関数合成というのは $g \circ f (x) = g(f(x))$ という数学でやったかもしれないしやってないかもしれないアレです。

配列でいう`[hoge]`の作用版として`() => hoge`を使えば, 配列と同じように記述できます。

```javascript
const revisedRoll = dice6.flatMap(x => () => x + 2);
```

[^undefined-plus]: 厳密に言うと, JavaScript の場合は足すことはできますが, 和が定義されていないため勝手に文字列にキャストされて結合されるだけですので違うよクソってなります。

## モナドのパワは素晴らしいぞ

実のところ, これがモナドのすべてです。 **`flatMap`によっていい感じに合成できるもの**, がモナドなのです。 クッソ抽象的なので誰も簡潔に説明できません。 でも, そもそも「いい感じ」なので使うのはめっちゃ簡単です。

![モナドはいい感じに flatMap できるやつ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241027/d30e8ed2-994c-663e-6529-41211229161c.png)
*図2. [「ソフトウェアエンジニアとしてモナドを完全に理解する」](https://speakerdeck.com/jooohn/make-perfect-sense-of-monad)より。 執筆後にこのスライドを見つけたのですが完全に表現がかぶっててパクリ疑惑で炎上が避けられませんね。 ふえぇ。*

> こんなに単純なことだ！僕と同じような考えなら、以下の疑問を持っていることだろう: もしそんなに単純なら、なんで皆大騒ぎしてるの？何で単に「一つの物を使って別の物を計算する」パターンって呼べばいいんじゃないの？
>
> 取り敢えず言っておくと、その名前は長過ぎるのでダメ。次に、モナドは最初に数学者によって定義された。数学者は何にでも名前をつけるのが好きなのだ。数学はパターン探しの専門だが、探した後で何でもいいから名前を付けないと、そのパターンを後で探すのが難しくなるからだ。
>
> 　　――　[「モナドはメタファーではない」](https://eed3si9n.com/ja/monads-are-not-metaphors), Daniel Spiewak, e.e d3si9n

PureScript / Haskell では`flatMap`は`>>=`という演算子として定義されています。 また, `[hoge]`や`() => hoge`も抽象化され, `pure` / `return`という名前の共通した関数になっています。

```haskell
hoge = fuga >>= (\x -> pure $ x + 2) -- 配列でも作用でもこの式でOK
```

`flatMap` / `>>=`を使えば「いつもの処理」はだいたいなんでもできます。 たとえば 3D6 のやつは以下のようにできます。

```javascript
const roll3d6 = dice6.flatMap(x => dice6.flatMap(y => dice6.flatMap(z => () => x + y + z)));
```

とはいえ流石にここまで来るとややこしいですよね。 PureScript や Haskell の場合, `>>=`演算子の糖衣構文である`do`式があるため, より簡潔に書くこともできます。 これはモナドそのものの強みというよりかは, モナドという形で「いつもの操作」を抽象化したことによって実現できた「言語」の強みですが, 直観的でかなり強力です。

```haskell
roll3d6 = do
  x <- dice6
  y <- dice6
  z <- dice6
  pure $ x + y + z
```

今回は配列モナドと対照する形で作用モナドを扱いましたが, モナドでいい感じにできるパターンというのはかなり多いです。

たとえば**例外機構**は「失敗しない前提で処理を書くが, 失敗してたら一気に大域脱出する」のがモナドでいい感じにできますし, **非同期処理**は「非同期に得られる結果を使った処理を書いて, 実際に得られたタイミングで計算する」のがモナドでいい感じにできます。

一般的な言語では「失敗するかもしれない値」「非同期に得られる値」を普通の値のように記述するために専用の文法(`try` / `async`)を用意しますが, `flatMap`をいい感じに定義してモナドにしてしまえば, ひとつの`do`式で全部対応できるのでかなりの強みです。 今後「あのモダンな機能がほしい!」となったときに言語の拡張が必要ないのですから。

![暗黒面のパワーはすばらしいぞ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241027/1de4f8c6-2b15-0419-070f-8febda97fe41.png)
*図3. モナドは決して純粋関数型言語の暗黒面ではありません。 たぶん。 ごめんちょっと自信ないや。*