Cats Effect の７つの型クラス

Scala の IO モナドライブラリ Cats Effect に含まれる 7つの型クラスのまとめ

## はじめに
Cats Effect の解説では、IO モナドを中心に語られることが多いが、この記事では型クラスの観点で広く浅くまとめてみる。

[Cats Effect](https://typelevel.org/cats-effect/) のバージョンは 1.2.0 とした。

## ７つの型クラス
Cats Effect の７つの型クラスは、Cats 本体の `MonadError` と `Defer` から始まる、下図のような継承ツリーを構成する（実装必須メソッドだけ記入した）。

### 構成
```
(MonadError)                        
     ＼　　　　　             (Defer)        * MonadError と Defer は cats 本体
        Bracket           ／
      -----------      ／
      bracketCase    ／
      　 　＼       ／
             Sync          LiftIO
            -------       --------
            suspend        liftIO
            delay         ／
         　　 　＼　     ／
                 Async
               ---------
                 async
                 asyncF
         　　　／  　    ＼　
     Concurrent         Effect
     ----------        --------
       start           runAsync
      racePair           ／
     cancelable        ／
        　　＼　      ／
          ConcurrentEffect
          ---------------
           runCancelable
```
以降、個別に見ていく。各型クラスのサブタイトルは[この図](https://typelevel.org/cats-effect/img/typeclasses-cheat-sheet.png)から適当に訳した。

## Bracket 〜 安全なリソース確保と解放
```scala
trait Bracket[F[_], E] extends MonadError[F, E]
```
`F[_]` が文脈で、`E` はエラー型。`MonadError` より上の継承ツリーは最後の補足に書いておいた。

#### 主なメソッド

```scala
def bracketCase[A, B](acquire: F[A])(use: A => F[B])(release: (A, ExitCase[E]) => F[Unit]): F[B]
def bracket    [A, B](acquire: F[A])(use: A => F[B])(release: A                => F[Unit]): F[B]

def uncancelable[A](fa: F[A]): F[A]

def guarantee    [A](fa: F[A])(finalizer: F[Unit])               : F[A]
def guaranteeCase[A](fa: F[A])(finalizer: ExitCase[E] => F[Unit]): F[A]
```
- **bracket**/**bracketCase**: *acquire*、*use*、*release* の3段階による安全なリソース操作を提供。
- **uncancelable**: キャンセルできない操作の提供。意味的には `bracket` に、*acquire* のみを与えたもの。
- **guarantee**/**guaranteeCase**: `try {} finally {}` に相当。`uncancelable` とは逆に、`bracket` に、*use*、*release* のみを与えて実現。

#### 主な法則

```
F.guarantee    (fa)(f) <-> F.bracket    (F.unit)(_ => fa)( _     => f)
F.guaranteeCase(fa)(f) <-> F.bracketCase(F.unit)(_ => fa)((_, e) => f(e))
```

#### メモ
- `ExitCase` は以下の種類
  - **Completed**: 普通に終わった場合
  - **Error(e)**: エラーが発生した場合
  - **Canceled**: `Fiber#cancel` などでタスクがキャンセルされた場合
- `bracket`/`guarantee` は、内部で `ExitCode` を無視する実装になっている。
- スコープに `Bracket[F]` があれば、`Kleisli` のインスタンスも自動的に提供される。

[Gist](https://gist.github.com/yasuabe/25f648589e91c97e97f29f6e9a47ecb1)

## LiftIO 〜 IO[A] から F[A] への変換
```scala
trait LiftIO[F[_]]
```
#### 主要メソッド

```scala
def liftIO[A](ioa: IO[A]): F[A]
```
- **liftIO**: `IO[A]` から `F[A]` への変換

#### メモ
- スコープに `LiftIO[F]` があれば、`EitherT`、`Kleisli`、`OptionT`、`StateT`、`WriterT`、`IotT` の `LiftIO` インスタンスも自動的に提供される。
- ※ [Cats Effect 公式ドキュメント](https://typelevel.org/cats-effect/typeclasses/liftio.html)の LiftIO 解説サンプルでは `IO` の unsafe 系メソッドを使っているが、実戦では、どのレイヤーが`IO[_]`でどのレイヤーが`F[_]`かなど、**境界**を工夫するとよいかもしれない。

[Gist](https://gist.github.com/yasuabe/9d97d41bed4b64fae0c9096d1db08e2d) 

## Sync 〜 Bracket × Defer + 同期サスペンド
```scala
trait Sync[F[_]] extends Bracket[F, Throwable] with Defer[F]
```
エラー型を `Throwable`に固定した `Bracket` と、Cats 本体の `Defer` を継承。

#### 主なメソッド

```scala
def suspend[A](thunk: => F[A]): F[A]
def delay[A]  (thunk: => A):    F[A] = suspend(pure(thunk))
...
```
- **suspend**: *by-name* で `F[A]` を受け取って、*by-name* じゃない`F[A]` を返すが、評価自体は遅延される。`Defer#defer` のエイリアスでもある。名前からなんとなく「一時停止（*pause*）」的な操作を連想してしまうが、そうではない[^suspend_naming]。
- **delay**: `suspend` と `Applicative#pure` を用いて実装されている。

#### 主な法則
`F` を `IO` とすると、下記の関係がなりたつ。

```
Sync[IO].delay(thunk)   <-> IO(thunk)         <-> IO.delay(thunk)
Sync[IO].suspned(thunk) <-> IO(thunk).flatten <-> IO.suspend(thunk)
```
[^suspend_naming]: suspend のネーミングについては[議論](https://github.com/typelevel/cats-effect/issues/152)があったらしい。Scalaz/Cats の Free monad のトランポリン実装や、Scalaz の Task と合わせたネーミングなのだという。

[Gist](https://gist.github.com/yasuabe/5f9d990a752ac91dca989927ae5c21d5)

## Async 〜 LiftIO × Sync + 非同期サスペンド
```scala
trait Async[F[_]] extends Sync[F] with LiftIO[F] {
```
#### 主なメソッド
ぱっと見わかりにくいかもしれないが、`Either[Throwable, A] => Unit` はコールバック関数で、 `Async[F]` のインスタンスから `async`/`asyncF` に渡されてくる。

```scala
def async[A] (k: (Either[Throwable, A] => Unit) => Unit):    F[A]
def asyncF[A](k: (Either[Throwable, A] => Unit) => F[Unit]): F[A]
def never[A]:                                                F[A]
...
```
- **async**: ジェネレータ関数`k`[^generator_func]から非同期タスク `F[A]` を作るビルダ。`k` の実装としては、渡されたコールバックに適当な `Either[Throwable, A]` 値を渡したりしつつ、最後に `Unit` を返せばよい。コールバックの呼び方としては、`k` の中で別スレッド（ `Future.onComplete` など）に書いても良いし、なんなら後述の `never` のように呼ばない（終わらない）という実装もある。
- **asyncF**: `async` と似ているが、`k`の結果型が `F` の文脈に入ったものになる。
- **never**: 何もせず、しかし終わらないメソッド。`F.async(_ => ())` と同じ。

[^generator_func]: `Concurrent#cancelableF` のコメントで *generator function* と書かれていたので「ジェネレータ関数」とした。"inject"される関数という表現もよくある。

#### 主な法則
`Async.async`、`Async.asyncF` と `Sync#delay` との間に、以下の関係がある。

```
F.async(k) <-> F.asyncF(cb => F.delay(k(cb)))
```

#### 主なコンパニオンオブジェクトの要素

- **shift** 関数: 与えられた `ExecutionContext` でのシフト。`IO.shift` のジェネリック版。
- **memoize** 関数: `F[A] => F[F[A]]`となるメモ化関数。

#### メモ
- `Async[F]` がスコープにあれば、`EitherT`、`OptionT`、`StateT`、`WriterT`、`Kleisli`、`IorT`のインスタンスも自動的に得られる。

[Gist](https://gist.github.com/yasuabe/6f01bde84d9e687debbfd4a462884247)

## Concurrent 〜 Async + 並行なスタートとキャンセル
```scala
trait Concurrent[F[_]] extends Async[F]
```
`Fiber` を利用した並行スタート／キャンセルを `Async` に付与したもの。

#### 主なメソッド
```scala
def start[A](fa: F[A]):                                                 F[Fiber[F, A]]
def cancelable[A](k: (Either[Throwable, A] => Unit) => CancelToken[F]): F[A]

def race    [A, B](fa: F[A], fb: F[B]): F[Either[A,                B]]
def racePair[A, B](fa: F[A], fb: F[B]): F[Either[(A, Fiber[F, B]), (Fiber[F, A], B)]]
```
- **start**: タスク `fa` を開始して `Fiber` インスタンスを得る。この `Fiber` を介して `cancel` したり `join` したりできる。
- **cancelable**: `CancelToken[F]`を返すジェネレータ関数`k`から、キャンセラブルなタスク `F[A]` を作る。`CancelToken[F]`は`F[Unit]` のことで、`F` の文脈で実行中のタスクをキャンセルするアクション。
- **race**/**racePair**: 文脈 `F` 上の2つのタスクを実行して結果を `Either` で得る。`race` は早いもの勝ちになるが、`racePair` では早く終わったタスクの結果と共に、終わっていない方の `Fiber` も返す。

#### 主な法則
`Bracket#uncancelable`、`Concurrent#cancelable`、`Async#async` の間に以下の関係がある

```scala
F.uncancelable(F.cancelable { cb => f(cb); token }) <-> F.async(f)
```

#### 主なコンパニオンオブジェクトの要素

- **timeout**/**timeoutTo** 関数: `F.race` を応用して、タスクにタイムアウト付ける関数
- **cancelableF** 関数: `F.cancelable` と似ているが、ジェネレータ関数の結果型が、`CancelToken[F]` ではなく `F[CancelToken[F]]`となるもの

#### メモ
- スコープ内で `F` が `Concurrent` ならば、`EitherT`、`OptionT`、`Kleisli`、`WriterT`、`IorT` のインスタンスも自動的に得られる。
- `Fiber` の簡略コードは以下のようなもの。

```scala
trait Fiber[F[_], A] {
  def join: F[A]
  def cancel: CancelToken[F]
}
```

[Gist](https://gist.github.com/yasuabe/8fe34189b59db77eab74184669b2e130)

## Effect 〜 Async + 遅延非同期評価
```scala
trait Effect[F[_]] extends Async[F]
```

#### 主なメソッド
```scala
def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[Unit]
def toIO[A](fa: F[A]): IO[A]
```
- **runAsync**: `IO#unsafeRunAsync` のジェネリックで安全な版(*safe and generic version*)。下のように `IO#unsafeRunAsync` と比較すると、「ジェネリックで安全な版」の意味がわかりやすいかもしれない。

```scala
def unsafeRunAsync             (cb: Either[Throwable,A]=>Unit    ): Unit        // IO
def       runAsync[A](fa: F[A])(cb: Either[Throwable,A]=>IO[Unit]): SyncIO[Unit]// Effect[F]

```
- **toIO**: `liftIO` と逆に、`F[A]` から `IO[A]`に変換する。

#### 主な法則

```scala
F.toIO(liftIO(ioa)) <-> ioa
```

#### メモ
- スコープ内で `F` が `Effect` ならば、`EitherT`、`WriterT` のインスタンスも自動的に得られる。
- `SyncIO` は非同期計算のない `IO`。

[Gist](https://gist.github.com/yasuabe/d372faa11a8bf507d3f604bf185e8f96)

## ConcurrentEffect 〜 Concurrent × Effect + キャンセルと並行評価
```scala
trait ConcurrentEffect[F[_]] extends Concurrent[F] with Effect[F]
```
#### 主なメソッド

```scala
def runCancelable[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[CancelToken[F]]
```
- **runCancelable**: `Concurrent#cancelable` と、`Effect#runAsync` を兼ね備えたようなもので、並べて比較すると下のようになる。

```scala
def cancelable[A]             (k: (Either[Throwable,A]=> Unit) => CancelToken[F]): F[A]
def runAsync[A]     (fa: F[A])(cb: Either[Throwable,A]=> IO[Unit]): SyncIO[Unit]
def runCancelable[A](fa: F[A])(cb: Either[Throwable,A]=> IO[Unit]): SyncIO[CancelToken[F]]
```
#### メモ
- スコープ内で `F` が `ConcurrentEffect` ならば、`EitherT`、`WriterT` の `ConcurrentEffect` インスタンスも自動的に得られる。

[Gist](https://gist.github.com/yasuabe/f823c1fdaffb158c003f0f1067332dcc)

## 補足
`Bracket` は `MonadError` を継承しているが、`MonadError` 自体は下のようなツリーになっている。なので `LiftIO` 以外は、以下の cats core の型クラス群の性質を持っていることになる。

```
Invariant
---------
 imap
  　＼
     Functor      Semigroupal
     ------       ----------
       map         product
      　 　＼       ／
             Apply
            ------
              ap
      　    ／ 　＼
    FlatMap       Applicative
    -------       ----------
    flatMap         pure
     　 　＼       ／         ApplicativeError
            Monad           -----------------
          　  ＼                raiseError
              　＼            handleErrorWith
      　        　 ＼        ／
                   MonadError
```