Skip to content

Instantly share code, notes, and snippets.

@gakuzzzz
Last active March 20, 2024 15:48
Show Gist options
  • Save gakuzzzz/ce10189cdd40427951bb5fadf18403b9 to your computer and use it in GitHub Desktop.
Save gakuzzzz/ce10189cdd40427951bb5fadf18403b9 to your computer and use it in GitHub Desktop.
MonadTransformer とは何か

MonadTransformer とは何か

注意書き

この記事は Monad がわかる人に向けた MonadTransformer の解説記事です。

すごいH本や FP in Scala などでモナドまではイメージが掴めたけれど、モナドトランスフォーマーが何かわからない、という層をターゲットに想定しています。

基本的に Functor, Applicative, Monad および型クラスについては把握しているものとしますので、この辺があやふやな方は別の資料などをご参照下さい。

サンプルコードとして Scala を利用します。ただし、説明の都合上、高階型引数について kind-projector? を用いた表記を使います。

例えば List[A] 型のモナドインスタンスは通常 Monad[List] 型として表しますが、この資料では Monad[List[?]] と表記します。これは型引数がネストした際に比較しやすくするためです。

それでは早速いきましょう。

Functor は合成できる

まず 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))

ListOption がネストした構造に対してダイレクトに map を実行できることが伝わるでしょうか。

Applicative は合成できる

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))

ListOption がネストした構造に対してダイレクトに apply2 などを実行することができます。

Monad は合成できない???

そして FunctorApplicative が合成可能である事とは打って変わって 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 さんの記事 合成できるモナド、モナドが合成できる時 がわかりやすいのでお勧めです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment