IOモナド
IOはもちろんモナドである。
入出力という副作用を扱うために用いられる。
main も IO () という型の変数である。 (関数ではない)
モナドだから return で値を中に入れることができる。
hoge::Int -> IO Int hoge x = return x main :: IO () main = do let ma = hoge 5 a <- ma print a -- 5
モナドだから join で一皮むくことができるが、 join で裸にはできない。
import Control.Monad main = do let ma = return 5 :: IO Int let mma = return ma let ma' = join mma -- let a' = join ma' -- エラー a' <- ma' print a' -- 5
モナドだから fmap で中の値を関数に渡すことができる。
main = do let ma = return 5 :: IO Int let mb = fmap (*2) ma b <- mb print b -- 10
モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。
hoge :: Int -> IO Int hoge x = return (x * 2) main = do let ma = return 5 let mb = ma >>= hoge b <- mb print b -- 10
モナドだから do構文が使える。main じたいが IO() 型だから、これは今まで見て来たとおりである。
do構文を使うと、関数の本筋と無関係な出力もあたかも手続き型言語のように記述できる。
hoge :: Int -> IO Int hoge x = do putStrLn "Hello, world!" -- 関数の本筋と無関係な出力 return (x * 2) main = do let mb = hoge 5 print =<< mb -- 10
入力も扱える。
hoge :: IO Int hoge = do putStrLn "Enter an integer:" -- 出力 s <- getLine -- 入力 let x = read s :: Int return x main = do x <- hoge print x
またIOモナドの中では IORef が使える。IORef は変更可能な(破壊的代入ができる)変数のようなものである。
IORef は newIORef で生成し、readIORef で値を取得し、writeIORef で値を更新する。
import Data.IORef main = do ma <- newIORef 1 -- ma :: IORef Integer a <- readIORef ma -- a :: Integer print a -- 1 writeIORef ma 2 a' <- readIORef ma print a' -- 2
関数で IORef の値を更新するときは、writeIORef ではなく modifyIORef を用いる。
import Control.Monad import Data.IORef sum' :: [Int] -> IO Int -- 戻り値にIOがつきまとう sum' xs = do v <- newIORef 0 -- 初期値 forM_ xs $ \i -> modifyIORef v (+ i) -- 更新 readIORef v main = do print =<< sum' [1..10] -- 55
しかしIOモナドの外に値を持ち出すことができない。(IOがつきまとう。)
STモナド
IOモナド / IORef と異なり、STモナド / STRef は runST で値を外に持ち出すことができる。
STモナドは破壊的代入を内部に閉じ込めて、計算結果のみを取り出すのに用いる。
STRef では、newIORef / readIORef / writeIORef / modifyIORef に代えて
newSTRef / readSTRef / writeSTRef / modifySTRef を用いる。
import Control.Monad import Control.Monad.ST import Data.STRef sum' :: [Int] -> Int -- 戻り値が裸 sum' xs = runST $ do -- runSTでSTモナドから値を取り出す v <- newSTRef 0 -- 初期値 forM_ xs $ \i -> modifySTRef v (+ i) -- 更新 readSTRef v main :: IO () main = do print $ sum' [1..10] -- 55
上記の例では sum' 関数の do構文の部分が STモナドだが、そのことがやや分かりにくい。
少し書き換えると下記のようになる。
import Control.Monad import Control.Monad.ST import Data.STRef sum' :: [Int] -> ST s Int -- STでくるんだ値を返す sum' xs = do v <- newSTRef 0 -- 初期値 forM_ xs $ \i -> modifySTRef v (+ i) -- 更新 readSTRef v main :: IO () main = do let a = runST $ sum' [1..10] -- runSTでSTモナドから値を取り出す print a -- 55
モナドだから return で値を中に入れることができる。
import Control.Monad.ST hoge :: a -> ST s a -- 内部で使用する状態型 s があることに注意 hoge x = return x main :: IO () main = do let ma = hoge 5 let a = runST ma print a -- 5
モナドだから join で一皮むくことができるが、 join で裸にはできない。
import Control.Monad import Control.Monad.ST hoge :: a -> ST s a hoge x = return x main :: IO () main = do let ma = hoge 5 :: ST s Int let mma = return ma :: ST s (ST s Int) let ma' = join mma -- ST s Int -- let a' = join ma' -- エラー let a' = runST ma' print a' -- 5
モナドだから fmap で中の値を関数に渡すことができる。
import Control.Monad.ST hoge :: a -> ST s a hoge x = return x main :: IO () main = do let ma = hoge 5 :: ST s Int let mb = fmap (*2) ma let b = runST mb print b -- 10
モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。
import Control.Monad.ST hoge :: Int -> ST s Int hoge x = return (x * 2) main :: IO () main = do let ma = hoge 5 let mb = ma >>= hoge let a = runST ma let b = runST mb print a -- 10 print b -- 20
またすでに見た通り、モナドだから do構文が使える。