引き続き、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]
のラッパーとなる。