Haskellのモナドいろいろ (2)

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構文が使える。