| Prev: モナドの合成は難しい | TOC: 目次 | Next: 標準モナド変換子 |
モナド変換子はモナドの合成を容易にする、標準モナドの特別版です。 モナド変換子の型構築子はモナド型構築子に関してパラメータ化されており 合成されたモナドの型を生成します。
型構築子は Haskell のモナドでは基盤としての役割をはたします。
Reader r a が 内部の型が a で、
r という型の環境をもつ Reader モナドの値の型であことを
思い出してください。型構築子 Reader r は、
Monad クラスのインスタンスで、
runReader::(r->a) 関数はその Readerモナドの計算を実行し、
型 a の結果を返します。
ReaderT とよばれるReader モナドの変換子版
が存在して、
追加パラメータとしてモナド型構築子が付け加わります。
ReaderT r m a は Reader が
ベースのモナドで、m が 内部モナド
であるような合成されたモナドの値の型です。
ReaderT r m はモナドクラスのインスタンスであり、
runReaderT::(r -> m a) 関数は、合成された
モナドの計算を実行し、m a という型の結果を返します。
モナドの変換子版を使うと、簡単に合成モナドを生成できます。
ReaderT r IO は Reader と IO の合成モナドです。
また、変換子版モナドを恒等モナドに適用して、非変換子版のモナドを
生成することも可能です。つまり、
ReaderT r Identity は Reader r
と同じモナドになります。
もし、コードのコンパイル中に類エラーが出たら、それは型構築子が正しく
使われていないということです。正しい数のパラメータを型構築子に渡したか
を確認し、複雑な型の式に括弧を付け忘れていないかを確かめてください。
モナド変換子で作った合成モナドを使うと、内部モナドの型の明示的な管理を しなくてすみます。その結果、わかりやすく、単純なコードになります。 その計算の内部で do ブロックを追加するかわりに、内部モナドの値を 処理するには、もちあげ演算を使って内部モナドから、合成モナドへ 関数を持ち出すことができます。
非モナド関数をモナドにもちあげるのに使う liftM 族の関数を
思い出してくさい。各変換子は、あるモナド計算を合成モナドへもちあげる関数
lift を提供します。多くの変換子は liftIO 関数も
提供しており、この関数は lift を IO モナドの
計算をもちあげることに最適化したものです。実際の動きを見るために、
前の Continuation モナドの例をさらに発展させましょう。
| example21.hs で使えるコード |
|---|
fun :: IO String
fun = (`runContT` return) $ do
n <- liftIO (readLn::IO Int)
str <- callCC $ \exit1 -> do -- "exit1" の定義
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do -- "exit2" の定義
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) $ do liftIO $ putStrLn "Enter a number:"
x <- liftIO (readLn::IO Int)
exit2 x
when ((length ns) < 7) $ do let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns') -- 2 レベル脱出
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
ContT を使ったこの関数、つまり Cont の
変換子版と、元々のバージョンのものとを比べると、モナド変換子を
つかうと変更がほとんど目立たないことがわかります。
| 例 19 の入れ子になったモナド | 例 21 の変換子を用いた合成モナド |
|---|---|
fun = do n <- (readLn::IO Int)
return $ (`runCont` id) $ do
str <- callCC $ \exit1 -> do
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) (exit2 n)
when ((length ns) < 7) $ do
let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns')
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
fun = (`runContT` return) $ do
n <- liftIO (readLn::IO Int)
str <- callCC $ \exit1 -> do
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) $ do
liftIO $ putStrLn "Enter a number:"
x <- liftIO (readLn::IO Int)
exit2 x
when ((length ns) < 7) $ do
let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns')
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
計算の途中に入出力を追加する影響は、モナド変換子をつかうと狭い範囲に 閉じ込められます。これを、同じ結果を得るために、手で合成したモナドを 使う場合に必要な変更と比べてみて 下さい。
| Prev: モナドの合成は難しい | TOC: 目次 | Next: 標準モナド変換子 |