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ブロック内で用いることが出来ます。