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 s
は Monad
クラスのインスタンス
であり、同時に、MonadState s
クラスのインスタンスです。
それゆえ、StateT s m
も Monad
と
MonadState s
のクラスの一員でなければなりません。
さらに、m
が MonadPlus
のインスタンスなら、
StateT s m
も MonadPlus
の一員
でなければなりません。
StateT s m
をMonad
のインスタンス
として定義すると
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 s
を MonadTrans
クラスのインスタンスと
することです。
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: さらにモナド変換子の例 |