Monda Transformers

引き続き、Scala with Catsを読んでいく。

Monad Transformers

Monadはネスト化されたfor-comprehensionsによってコードを肥大化させる可能性がある。

Exercise: Composing Monads

M1, M2というモナドを合成したComposed[A]という型のflatMapを実装できるかという問題。

import cats.Monad
import cats.syntax.applicative._

object Exercise5_1 extends App {

  def compose[M1[_]: Monad, M2[_]: Monad] = {
    type Composed[A] = M1[M2[A]]

    new Monad[Composed] {
      def pure[A](a: A): Composed[A] =
        a.pure[M2].pure[M1]

      def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] = ???
    }
  }
}

M1, M2について知らずにflatMapの一般的な定義を書くことは不可能である。

しかし、どちらかのモナドについて知っていれば、上記のコードを書くことができる。

たとえば、M2がOptionだとすると、flatMapの定義はこうなる。

def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] =
  fa.flatMap(_.fold[Composed[B]](None.pure[M1])(f))

上記の定義で出てくるNoneというのはOption固有の概念で、一般的なモナドには出てこない。

Optionを他のモナドと合成するためにより詳細な情報が必要になる。

他のモナドについても同様で、flatMapメソッドを書くのに役に立つ。これはモナドトランスフォーマーの背景にあるアイデアである。

Catsは様々なモナドのためにトランスフォーマーを定義し、それぞれが他のモナドと合成するために必要な追加の情報を提供している。

A Transformative Example

Catsではたくさんのモナドトランスフォーマーを提供しており、それらの名前のサフィックスにはTという名前がつけられている。(e.g. EitherT)

EitherTはEitherと他のモナドを合成し、OptionTはOptionと他のモナドを合成する。

Monad Tranformers in Cats

各モナドトランスフォーマーはデータ型でcats.dataに定義されており、モナドのスタックをラップして新しいモナドを生成することが可能。

Summary

モナドトランスフォーマーの型シグネチャは内から外に向かって書かれている。

例えば、EitherT[Option, String, A]というシグネチャの場合はOption[Either[String, A]のラッパーとなる。

参考資料

MonadTransformer とは何か · GitHub

everpeace.hatenadiary.org