ReaderT ってどんなときに使うのだろうと思うたが, IO 使いながら socket とか扱ってプログラムする上で引数がいっぱい付いてしまってしょうがない, といった場面なんかに, それらを読み取り専用の大域的な状態として扱ったほうがすっきりするのだなということを dons さんの IRC bot を作る記事を見て納得した. io 関数と forever 関数が素敵です.
実際に下に Echo monad なるモナドを書いてみた(type constractor使っただけだけど). 基本的には IO からの入力を echo するプログラム. Easter Egg と入力すると隠れキャラ的なものが登場します. ヘビに食べられたゾウっぽいなにか. モジュール書くくらい規模が大きくならないと, なかなか真価を発揮しないようにおもうた.
module Main ( main ) where import System.IO import System.Exit (exitWith, ExitCode(ExitSuccess)) import Control.Monad.Reader (ReaderT(..), runReaderT, asks, liftIO, lift) -- Echo monad type Echo = ReaderT AI IO data AI = AI { easterEgg :: String } main :: IO () main = initialize >>= runReaderT talk where initialize = return ( AI { easterEgg = ee } ) ee :: String ee = concat $ [c 31,"/",b 4,t,c 23,b 7,"/",c 6,t, c 22,"/",c 15,t,c 21,"/",c 16,"|\n", c 3,b 17,c 18,b 8,t,c 3,b 45,"\n"] where t :: String t = "\\\n" c :: Int -> String c 0 = [] c n = ' ': c (n-1) b :: Int -> String b 0 = [] b n = '-': b (n-1) talk :: Echo () talk = forever $ do lift (liftIO (putStr "> " >> getLine)) >>= \s -> case s of "Easter Egg" -> do ee <- asks easterEgg io (putStrLn ee) "exit" -> exit "quit" -> exit str -> io (putStrLn $ str) where forever a = a >> forever a exit = io (exitWith ExitSuccess) io :: IO a -> Echo a io = liftIO
で実行してみた結果.
$ ghci echo.hs
GHCi, version 6.8.2: http://www.haskell.org/ghc/ :? for help
Loading package base ... linking ... done.
Ok, modules loaded: Main.Prelude Main> mainLoading package mtl-1.1.0.0 ... linking ... done.
> hi
hi
> moo
moo
> Easter Egg
/----\
-------/ \
/ \
/ |
----------------- --------\
---------------------------------------------
> it's an elephant being eaten by a snake, of course
it's an elephant being eaten by a snake, of course
> quit
*** Exception: exit: ExitSuccess
Prelude Main>