Contの合成がしたい

# 前置き
継続モナド`Cont[R, A]`は「どんな事があっても値を返したい」というシーンでのエラーハンドリングで非常に便利です。
ですが、`R`が同一の`Cont`同士でしか合成できないため、時々型が合わなくて困ることがありますよね。

そういった場合の解決策を考えていこうと思います。[^1]

なお、`ContT[F[_], R, A]`でもほとんどの内容は同じなため、すべて`Cont[R, A]`で記述します。

[^1]: のつもりでしたが、日付が変わってしまいそうなため`IndexedCont`の紹介で終わってしまいました。

# `Cont[R, A]`のおさらい
`Cont[R, A]`の`map`, `flatMap`の型定義は以下のような感じです。

```scala
class Cont[R, A](run: (A => R) => R) {
  def map[B](f: A => B): Cont[R, B] = ...
  def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = ...
}
```

このため、`for`式の流れは以下のような感じです。

```scala:sourceのイメージ
def contA: Cont[R, A]
def contB(a: A): Cont[R, B]
...

val result: Cont[R, D] = for {
  a <- contA
  b <- contB(a)
  c <- contC(b)
  d <- contD(c)
} yield d

result.run(d => dToR(d)) // => R
```

```plaintext:処理のイメージ
contA    : A----------------------------R
contB(a) :   B--------------------------R
contC(b) :     C------------------------R
contD(c) :       D----------------------R
```

ある終了地点Rに向かって、`flatMap`するたびに少しずつ近付いていき、最後に`run`で終着点に到達するイメージですね。

同一の目標`R`を目指している`Cont`同士なら問題なく合成できるということです。

# 違う目標`R1`と`R2`を目指す`Cont`は合成できないのか
昔のソースコードと戯れるときなど、ちょっと違う`Cont`同士を合成できないものかと悩むシーンはそれなりにあると思います。

いくつかの場合に分けて方法を考えてみたいと思います。
## 開始点・終着点が他方の`Cont`に内包される場合
`Cont[R1, A]`と`Cont[R2, B]`の関係が以下のようになっている場合です。

```scala
Cont[R1, A]: A-------------------------R1
Cont[R2, B]:        B---------R2
```

外部入力のString(`A`)からID(`B`)を作って、そのIDからEntity(`R2`)を生成しResult(`R1`)を返却するような処理を想定すると、下のように定義できます。

```scala
cont1: Cont[Result, String]
cont2: Cont[ID, Entity]
```

こういった場合は処理は単純で、`IndexedCont[R, O, A]`型を使用すると簡単に`flatMap` できます。

### `IndexedCont[R, O, A]`
なじみの薄い方向けに、定義を記述しておきます。

#### 定義
```scala
class IndexedCont[R, O, A](run: (A => O) => R) {
  def map[B](f: A => B): IndexedCont[R, O, B] =
    new IndexedCont[R, O, B] { (g: B => O) =>
      run(f.andThen(g))
    }

  def flatMap[B](f: A => IndexedCont[O, E, B]): IndexedCont[R, E, B] =
    new IndexedCont[R, E, B] { (g: B => E) =>
      run(a => f(a).run(g))
    }

  def imap[E](f: R => E): IndexedCont[E, O, A] =
    new IndexedCont[E, O, A] { (g: A => O) =>
      f(run(g))
    }

  def contramap[I](f: I => O): IndexedCont[R, I, A] =
    new IndexedCont[R, I, A] { (g: A => I) =>
      run(g.andThen(f))
    }
}
```

#### 意味
基本的には`Cont[R, A]`と同様のものですが、`run`が受け取る関数が`A => R`ではなく`A => O`になっています。

現在の値`A`から`R`に向かう途中の型`O`までの処理(継続)を渡してくれたら、`O => R`はこのオブジェクトの中で勝手にやるよという型です。

```scala
cont1: IndexedCont[R, O, A]: A-------------O-----R
cont2: IndexedCont[O, E, B]:   B--------E--O
```

`Cont[R, A]`は`IndexedCont[R, O, A]`の`R == O`という特殊形なので、型エイリアスを使って以下のように定義できます。

```scala
type Cont[R, A] = IndexedCont[R, R, A]
```

[scalazの中](https://github.com/scalaz/scalaz/blob/7df076d71be133a3b595bb844a6c117fac470a15/core/src/main/scala/scalaz/package.scala#L324-L364)では実際にこのような感じで定義されています。

#### flatMap
`IndexedCont[R, O, A]`には以下のような型が登場します。

```plaintext
R: 最終的なゴール
O: 中間地点
A: 現在の値
A => O: runが受け取る「継続」
```

上記「継続」を受け取りなんらかの方法で`O`ができた後、そこから`R`を作る方法`O => R`は`IndexedCont`の中に内包されています。
外から触る方法はありませんし、`O`を使ってくれているかどうかもそのインスタンス次第です。

```scala
def contA: IndexedCont[R, O, A]
def contB(a: A): IndexedCont[O, E, B]
def contC(b: B): indexedCont[E, E2, C]

val result: IndexedCont[R, E2, C] = for {
  a <- contA
  b <- contB(a)
  c <- contC(b)
} yield c

result.run(c => cToE2(c)) // => R
```

```plaintext:処理のイメージ
contA    : A-------------------------O--R
contB(a) :   B--------------------E--O
contC(b) :     C-------------E2---E
result   :     C-------------E2---------R
```

`A`から`C`までの処理は`flatMap`の中で実行されます。
`C => E2`は`run`に渡される「継続」です。
`E2 => E`は`contC`の中で実行され、
`E => O`は`contB`の中で、
`O => R`は`contA`の中でそれぞれ実行されます。

### Contからの変換
前述したように`Cont[R, A] = IndexedCont[R, R, A]`なので、`imap`や`contramap`を使用すると変換が可能です。

#### imap
```scala:imap
def rToI(r: R): I = ...
val cont1: Cont[R, A] = ...
val cont2: IndexedCont[I, R, A] = cont1.imap(rToI)

cont1 : A-----------------R
cont2 : A-----------------R---I
```

`imap`は上記のように、`R`が出来上がった後の処理を足す事ができます。
一度作成した`cont2`から`cont1`を作り出す方法は無いので注意してください。

もちろん`R => I`をキャンセルするような`I => R`で`imap`すれば型は元に戻りますが、内部で処理されなくなるわけではありません。

#### contramap
```scala:contramap
def oToR(o: O): R = ...
val cont1: Cont[R, A] = ...
val cont2: IndexedCont[R, O, A] = cont1.contramap(oToR)

cont1 : A-----------------R
cont2 : A--------------O--R
```

`contramap`は`imap`とは逆に、`R`の前の処理を追加するものです。

### 最初の課題へ
課題はこちら

```scala
Cont[R1, A]: A-------------------------R1
Cont[R2, B]:        B---------R2
```

`IndexedCont`だと考えると、`imap`か`contramap`を使えば合成できそうですね。

```scala
IndexedCont[R1, R1, A]: A-------------------------R1
IndexedCont[R2, R2, B]:        B---------R2

def contA: Cont[R1, A] = ...
def contB(a: A): Cont[R2, B] = ...
def r2ToR1(r2: R2): R1 = ...

val result: IndexedCont[R1, R2, B] = for {
  a <- contA.contramap(r2ToR1)
  b <- contB(b)
} yield b

IndexedCont[R1, R2, A]: A----------------R2-------R1
IndexedCont[R2, R2, B]:        B---------R2

// または

val result: IndexedCont[R1, R2, B] = for {
  a <- contA
  b <- contB(b).imap(r2ToR1)
} yield b

IndexedCont[R1, R1, A]: A-------------------------R1
IndexedCont[R1, R2, B]:        B---------R2-------R1
```

## 開始点・終着点が他方の`Cont`に内包されない場合
この場合は処理の組み合わせによって、どちらかのContをrunしたうえでつなげていくことになります。

ですが、アドベントカレンダーなのに日付が変わってしまいそうなのでまたの機会に記述したいと思います。

# おわりに
Contって便利ですよね。
少しでも使う人が増えますように。

アドベントカレンダーなのにギリギリ更新ですみません。