Haskell: Readerモナドの実験
Readerモナドで定義されているlocal関数とasks関数の実験をしてみた。
local関数とasks関数を使うと、環境(データを記憶する領域のようなもの)を局所的に操作できることがわかった。
これらの関数をうまく利用すれば、プログラミング言語でおなじみの「スコープ」の概念を綺麗に表現できるようになると思われる。
Haskellでインタプリタを実装したい開発者にとっては嬉しい話に違いない。
コードは以下の通り。ReaderTでReaderモナドとIOモナドをレイヤー化している(多分)。こうしておけば、liftIO関数とputStr関数を組み合わせて、IOも同時にできてしまうようだ。(実はまだあんまりよくわかっていない) つまり、昔ながらのprintデバッグが可能になる。
% vim reader.hs import Control.Monad.Reader data Env = Env { envMsg :: String } emptyEnv = Env { envMsg = "" } append msg env = env { envMsg = (envMsg env) ++ msg } level0 :: String -> ReaderT Env IO String level0 msg = do local (append msg) level1 level1 :: ReaderT Env IO String level1 = do msg <- asks envMsg debug ("level1-1: " ++ msg) local (append ", geeks!") level2 debug ("level1-2: " ++ msg) return (msg ++ ", world") level2 :: ReaderT Env IO String level2 = do msg <- asks envMsg debug ("level2-1: " ++ msg) local (append " How are you?") level3 debug ("level2-2: " ++ msg) return (msg ++ "This will be ignored...") level3 :: ReaderT Env IO String level3 = do msg <- asks envMsg debug ("level3: " ++ msg) return (msg ++ "This will be ignored too") debug :: String -> ReaderT Env IO () debug s = do liftIO $ putStrLn s main :: IO () main = do result <- runReaderT (level0 "hello") emptyEnv putStrLn ("result: " ++ result)
実行すると以下のようになる。注目すべきは、level3を頂点にして、それ以降のメッセージが前のものに戻っていっている所。これは、環境が局所的に操作されている証拠。
% ghci reader.hs *Main> main level1-1: hello level2-1: hello, geeks! level3: hello, geeks! How are you? level2-2: hello, geeks! level1-2: hello result: hello, world
local関数は局所的(local)に環境を操作するから、localという関数名がついているのだと思う。
Perlにはlocal演算子があるけれども、そのlocal演算子と今回扱ったlocal関数は感覚的に似ている。
それにしても、Readerモナドの簡単なサンプルが見つからなくて苦労した。
今までPerlやPHPのようにドキュメントやサンプルが無数にあるのが当たり前な環境になれていたので、Haskellを勉強するようになってからそれらの有難さが身にしみてわかるようになった。