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: 標準モナド変換子 |