この記事は Monad がわかる人に向けた MonadTransformer の解説記事です。
すごいH本や FP in Scala などでモナドまではイメージが掴めたけれど、モナドトランスフォーマーが何かわからない、という層をターゲットに想定しています。
基本的に Functor
, Applicative
, Monad
および型クラスについては把握しているものとしますので、この辺があやふやな方は別の資料などをご参照下さい。
サンプルコードとして Scala を利用します。ただし、説明の都合上、高階型引数について kind-projector の ?
を用いた表記を使います。
例えば List[A]
型のモナドインスタンスは通常 Monad[List]
型として表しますが、この資料では Monad[List[?]]
と表記します。これは型引数がネストした際に比較しやすくするためです。
それでは早速いきましょう。
まず Functor
は合成することができます。
どういう意味かと言うと、任意の型 F[A]
, G[A]
について、Functor[F[?]]
と Functor[G[?]]
から Functor[F[G[?]]]
を定義することが可能という意味です。
具体的に言うと例えば Functor[List[?]]
と Functor[Option[?]]
から Functor[List[Option[?]]]
を作ることができます。
import scalaz._
import scalaz.std.list._
import scalaz.std.option._
val mf: Functor[List[?]] = implicitly
val mg: Functor[Option[?]] = implicitly
val mfg: Functor[List[Option[?]]] = mf compose mg
val v: List[Option[Int]] = List(Some(1), Some(2), None, Some(4))
mfg.map(v) { _ * 2 } // 結果: List(Some(2), Some(4), None, Some(8))
List
と Option
がネストした構造に対してダイレクトに map
を実行できることが伝わるでしょうか。
Functor
が合成可能であるように Applicative
も合成することができます。
つまり、任意の型 F[A]
, G[A]
について、Applicative[F[?]]
と Applicative[G[?]]
から Applicative[F[G[?]]]
を定義することが可能です。
import scalaz._
import scalaz.std.list._
import scalaz.std.option._
val mf: Applicative[List[?]] = implicitly
val mg: Applicative[Option[?]] = implicitly
val mfg: Applicative[List[Option[?]]] = mf compose mg
val v1: List[Option[Int]] = List(Some(2), Some(3))
val v2: List[Option[Int]] = List(None, Some(10))
mfg.apply2(v1, v2) { _ * _ } // 結果: List(None, Some(20), None, Some(30))
List
と Option
がネストした構造に対してダイレクトに apply2
などを実行することができます。
そして Functor
や Applicative
が合成可能である事とは打って変わって Monad
は一般的に合成することができません。
つまり、任意の型 F[A]
, G[A]
について、Monad[F[?]]
と Monad[G[?]]
から Monad[F[G[?]]]
を定義することができないという事です。
ただし G[A]
が特定の性質を満たす場合に限っては Monad[F[G[?]]]
を定義することが可能になります。
そこで、その性質を満たす G[A]
の具体的な型それぞれに対して、一つ一つモナドインスタンスを定義してしまおうという暴挙にでました。抽象化の放棄ですね。
その性質を満たす G[A]
の具体的な型にあたるのが Option[A]
や Either[L, R]
や Reader[A, B]
や Cont[A]
などなどです(他にもまだまだあります)。
つまり Monad[F[?]]
をつかって直接 Monad[F[Option[?]]]
や Monad[F[Either[L, ?]]]
をそれぞれ毎に定義してしまおうという訳です。
しかし、Monad[F[?]]
と Monad[F[Option[?]]]
が同時にスコープに存在すると型解決で衝突してしまい不便です。
そこで、Option[A]
や Either[L, R]
や Reader[A, B]
などのそれぞれの型毎に、F[A]
と合わせた値を表現するための新しいデータ型を定義してしまい、その新しいデータ型に対してモナドインスタンスを定義するようにします。
case class OptionT[F[_], A](value: F[Option[A]])
object OptionT {
// Monad[F[?]] を使って OptionT のモナドインスタンスを定義する
implicit def m[F[_]](implicit mf: Monad[F[?]]): Monad[OptionT[F[?], ?]] = ...
}
case class EitherT[F[_], L, R](value: F[Either[L, R]])
object EitherT {
// Monad[F[?]] を使って EitherT のモナドインスタンスを定義する
implicit def m[F[_], L](implicit mf: Monad[F[?]]): Monad[EitherT[F[?], L, ?]] = ...
}
case class ReaderT[F[_], A, B](value: Reader[A, F[B]])
object ReaderT {
// Monad[F[?]] を使って ReaderT のモナドインスタンスを定義する
implicit def m[F[_], A](implicit mf: Monad[F[?]]): Monad[ReaderT[F[?], A, ?]] = ...
}
この新しいデータ型の名前には、元になったデータ型がわかりやすいように、元のデータ型名 + T
という名前をつけるのが一般的になっています。
そしてこれら F[A]
と合わせた値を表現するための新しいデータ型を総称してモナドトランスフォーマーと呼びます。
これら MonadTransformer
にはモナドインスタンスが存在するわけなので、もちろん for式等での利用が可能になります。
import scalaz._
import scalaz.std.list._
import scalaz.std.option._
import scalaz.syntax.monad._
val v1: List[Option[Int]] = List(Some(2), Some(3))
val v2: List[Option[Int]] = List(None, Some(10))
for {
x <- OptionT(v1)
y <- OptionT(v2)
} yield x * y
// 結果: OptionT(List(None, Some(20), None, Some(30)))
つまり MonadTransformer
を使うことで、ネストした構造に対してダイレクトにMonadicな操作を行う事が可能になるのです。
まとめると、MonadTransformer
とは、異なる種類のモナドインスタンスを合成して一つのモナドインスタンスとして扱えるようにするデータ型の総称の事です。
上記でぼかして書いた Monad
が合成可能になる G[A]
の性質について詳しく知りたい方は、@everpeace さんの記事 合成できるモナド、モナドが合成できる時 がわかりやすいのでお勧めです。