みどりねこ日記

よくわからないけど、頑張りますよ。

Haskellモナドトランスフォーマー(2)

関数getPasswordとそれを利用するコードを簡潔に書くために、IOモナドにMaybeモナドの特性を与えるモナドトランスフォーマーを定義します。これを、モナドトランスフォーマーの慣例的な名付け方に従い、最初に与える特性を持つモナドの名前+Tとし、MaybeTと呼びます。

MaybeTは、m (Maybe a)のラッパーで、mにはどのようなモナドも入ることが出来ます。(ここではIOに注目します。)

newtype (Monad m) => MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)}

アクセサ関数runMaybeTを使うことによってMaybeTのなかに隠れた中身にアクセスできます。

モナドトランスフォーマー自身もモナドであるため、MaybeT mをMonadクラスのインスタンスとして置く必要があります。

instance Monad m => Monad (MaybeT m) where
	return = MaybeT . return . Just

returnは値をとって、それをJustによってMaybeとなり、普通のreturnによってモナドmにし、MaybeTコンストラクタを適用します。読みにくいですが、

return = MaybeT . return . return

でも大丈夫です。
それでは>>=演算子の方を見ていきます。

x >>= f = MaybeT $ do
	maybe_value <- runMaybeT x
	case maybe_value of
		Nothing -> return Nothing
		Just value -> runMaybeT $ f value

(>>=)演算子の定義をみることで、トランスフォーマーがどうして動くのか、どのようにMaybeの値を展開するのか、NothingまたはJustの値を受け取った時m Nothingまたはm (Just value)のようにMaybeTの中に包んでいくのかが理解できると思います。

doブロックの中でアクセサrunMaybeTがあるのに、MaybeTコンストラクタがdoブロックの前にあることが不思議になるかもしれません。しかし、>>=演算子を定義していないため、doブロックはMaybeT mのなかではなく、モナドmの中になくてはなりません。

技術的には、しなくてはならないことはこれで全部です。しかし、MaybeTを幾つかの他のクラスのインスタンスにしておくと便利です。

instance Monad m => MonadPlus (MaybeT m) where
	mzero = MaybeT $ return Nothing
	mplus x y = MaybeT $ do
		maybe_value <- runMaybeT x
		case maybe_value of
			Nothing -> runMaybeT y
			Just value -> runMaybeT x

instance MonadTrans MaybeT where
	lift = MaybeT . (liftM Just)

MonadTransは関数liftを宣言しており、この関数はmモナドからMaybeT mモナドに持ってくるときに便利な関数ですので、MaybeT mモナドの中のdoブロック内で用いることが出来ます。