Prev: 標準モナドの変換子バージョン TOC: 目次 Next: さらにモナド変換子の例

モナド変換子の解剖


この節では、標準ライブラリのより興味深い StateT の実装に ついて詳しく見ていきましょう。この変換子を研究しながら、自分のコードで モナド変換子を使う際に呼びおこせる、変換子の機構についての洞察を構築して いきます。先に進む前に、State モナド の節 を復習しておくとよいでしょう。

モナド定義の合成

単なる State モナドは次のような定義で構築されていました。

newtype State s a = State { runState :: (s -> (a,s)) }
StateT 変換子は以下のように定義されます。
newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }
State sMonad クラスのインスタンス であり、同時に、MonadState s クラスのインスタンスです。 それゆえ、StateT s mMonadMonadState s のクラスの一員でなければなりません。 さらに、mMonadPlus のインスタンスなら、 StateT s mMonadPlus の一員 でなければなりません。

StateT s mMonad のインスタンス として定義すると

newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }

instance (Monad m) => Monad (StateT s m) where
    return a          = StateT $ \s -> return (a,s)
    (StateT x) >>= f  = StateT $ \s -> do (v,s')      <- x s          -- 新しい値と状態を得る
                                          (StateT x') <- return $ f v -- 束縛された関数を適用して新しい変換 fn を得る
                                          x' s'                       -- その状態変換 fn を新しい状態に適用する

これを、 State s の定義と比較しましょう。ここの return の定義は、 内部モナドの return 関数を使っており、束縛演算子は do ブロックを使って、内部モナドの計算を実行しています。

StateT を使うすべての合成モナドが MonadState クラスのインスタンスなるよう宣言したいので、 get および put の定義を与えなければならない でしょう。

instance (Monad m) => MonadState s (StateT s m) where
    get   = StateT $ \s -> return (s,s)
    put s = StateT $ \_ -> return ((),s)

最終的には、MonadPlus のインスタンスと StateT を使う合成モナドが、MonadPlus のインスタンスであると 宣言したいわけです。

instance (MonadPlus m) => MonadPlus (StateT s m) where
    mzero = StateT $ \s -> mzero
    (StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s)

もちあげ関数を定義する

モナド変換子を完全にHaskellのモナドクラスに統合する最終段階は lift 関数を用意することで StateT sMonadTrans クラスのインスタンスと することです。

instance MonadTrans (StateT s) where
    lift c = StateT $ \s -> c >>= (\x -> return (x,s))

lift 関数は StateT という 内部モナドの計算を入力状態と結果を梱包する関数に変換するような 状態変換の関数を生成します。その結果、リスト(すなわち、List モナド の計算)を返す関数を StateT s [] へもちあげる ことになります。そこで、それは、 StateT (s -> [(a,s)]) を返す関数に なります。すなわち、もちあげられた計算は、入力状態から、 多重の (value,state) の組を生成します。 この効果は、StateT の計算を「fork」し、もちあげられた関数が返すリストの 値ごとに、計算の別の枝を生成します。もちろん、StateT を別のモナドに適用すると lift 関数に対応して別の セマンティクスを生じます。

ファンクタ

上でひとつのモナド変換子の実装を精査し、以前主張したように、モナドの 変換子バージョンを生成する魔法の公式はありませんでした。それぞれの 変換子の実装は、それが内部モナドに付加する計算効果の性質に依存する ことになります。

ではありますが、モナド変換子の理論には、いくつかの理論的な基盤があります。 ある種の変換子は内部モナドの使い方によってグループ化でき、それぞれの グループ内で変換子はモナド関数とファンクタを使って 導出できます。ファンクタは大雑把にいえば、写像演算 fmap :: (a->b) -> f a -> f b をサポートする型です。これについてもっと学びたければ、Mark Jones の Haskell のモナドテンプレートライブラリの契機になった、影響力のある 論文 をチェックしてください。


Prev: 標準モナドの変換子バージョン TOC: 目次 Next: さらにモナド変換子の例