複合モナドである StateT モナドを利用する目的は、その中で IO モナドの関数を利用するためだと思われている。実際 lift 関数を利用することで、 StateT モナドの中で IO モナドの関数 putStrLn "hello" は lift $ putStrLn "hello" を StateT モナド関数として使うことができる。 Prelude> import Control.Monad.State Prelude Control.Monad.State> foo = do {lift $ putStrLn "hello"} :: StateT String IO () Prelude Control.Monad.State> runStateT foo "" hello ((),"") したがって、次のように StateT モナドの関数である get とも混在して使うことができる。 Prelude Control.Monad.State> bar = do {x <- get; lift $ putStrLn x} :: StateT String IO () Prelude Control.Monad.State> runStateT bar "hello" hello ((),"hello") この意味で IO モナドは StateT モナドの中で使うことができると言える。ところが、runStateT bar "hello" の戻り値は IO ((),"hello") である。したがって、runStateT 関数は IO モナド関数である。すなわち、runStateT 関数は IO モナドの中の関数ということになり、今度は State T モナドが IO モナドの中で使われているということになる。内と外が逆になってしまう。 したがって、今度は runStateT 関数が他の IO モナド関数と一緒に IO モナドの中で使うことができるということになる。 Prelude Control.Monad.State> do {runStateT foo "hello"; putStrLn "world"} hello world StateT モナドの中に IO モナド関数が入っていたと思っていたのに runStateT 関数は IO モナド関数だったというモナド同士の内と外の関係は混乱する。しかし、lift $ putStrLn 関数は StateT モナドの中の関数であるが。そのままでは StateT モナドのプログラムは実行されない。それは、runStateT 関数の引数にすることによって初めてプログラムとして実行される。ところが、このプログラムの実行を担う runStateT 関数は StateT モナドのプログラムを実行後 IO a 型の IO モナド値を返すので IO モナド関数なのである。 StateT モナドと IO モナドの関数の内と外の関係が交錯してわかりづらいが、それぞれの関数が使われる場所に依存しているのだと考えるしかない。図にすると次のようになる。 do {runStateT (do {x <- get; lift $ putStrLn x}) "hello"; putStrLn "world"} だた、上の入れ子構造で見られる、StateT モナドと IO モナドのそれぞれの関数の立ち位置の違いが分かるようになれば、複合モナドの利用を納得して行うことができる。慣れるしかない。 StateT モナドのプログラム例として、簡単なポーランド記法のパーサを書いてみた。ポーランド記法の計算は、演算子が先頭に来て関数のような働きをする。たとえば、+ + 1 2 3 は、+ (+ 1 2) 3 の意味で (1+2) + 3 という計算になる。途中経過を数値スタックに積まずに再帰下降型のパースで計算することができ、カッコを使わないのが特徴だ。下のプログラムは、StateT モナド内で evaluate の戻り値を表示するようにしているので、再帰的計算の過程が分かる。(文頭のアンダースコアはスペースで置換する。) 元のプログラムは過去記事「IOモナドでスタックを利用する」に掲載している。同じところをぐるぐる回っているが螺旋状に少しずつ上昇していると信じたい。 実行例 *Main Control.Monad.State> main 1 2 2 3 6 ソースプログラム import Control.Monad.State
pop :: StateT [String] IO String pop = do __x <- get __put (tail x) __return (head x) evaluate :: StateT [String] IO String evaluate = do ____x <- pop ____case x of ______"*" -> do ________val1 <- evaluate ________lift $ putStrLn val1 ________val2 <- evaluate ________lift $ putStrLn val2 ________return $ show $ (read val1 :: Int) * (read val2 :: Int) ____ _ -> return x main = do __putStrLn =<< evalStateT evaluate ["*","*","1","2","3"]
by tnomura9
| 2019-05-27 02:51
| Haskell
|
カテゴリ
新型コロナウイルス 主インデックス Haskell 記事リスト 圏論記事リスト 考えるということのリスト 考えるということ ラッセルのパラドックス Haskell Prelude Ocaml ボーカロイド 圏論 jQuery デモ HTML Python ツールボックス XAMPP Ruby ubuntu WordPress 脳の話 話のネタ リンク 幸福論 キリスト教 心の話 メモ 電子カルテ Dojo JavaScript C# NetWalker ed と sed HTML Raspberry Pi C 言語 命題論理 以前の記事
最新のトラックバック
最新のコメント
ファン
記事ランキング
ブログジャンル
画像一覧
|
ファン申請 |
||