第8章はIOが何か、IOでできること、IOの実行タイミングについて。
副作用の分離
副作用(結局副作用は何なのか)を起こしてもいいのがIOだそうです。以上。
Hello World!
helloworld.hs
を下のように作って、
main = putStrLn "hello, world"
runhaskell helloworld.hs
−− あるいは
ghc -make helloworld
./helloworld
すればhello, world!と表示される。関数mainを実行する際にIOは実行される。
ここで型を見てみると、
> :t putStrLn putStrLn :: String -> IO ()
IO ()
である。関数には通常返り値が存在するが、IOを実行した関数からの返り値は無いのでダミーとして空のタプルが使われている。(つまりIO sometypeがmainの型。)
普通は書かないが、mainに型を与えてやって、
main :: IO() main = ...
としても同じ結果になる。
IOをまとめる
do構文やIOアクションから値を取り出すには<-
を使う。
main = do putStrLn "Hello, what's your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!")
> :t getLine getLine :: IO String
なのでgetLineはIOアクションを起こして、その結果の値をString型で返すことが分かる。この振る舞いはgetLineだけでなくIOをもつ関数一般にも言える。
そこで、このファイルは次のようにも書ける。
main = do foo <- putStrLn "Hello, what's your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!")
fooの型はただの空タプルなので意味はない。また最後のputStrLnはmainの型を調べるのに必要らしく、<-で束縛することはできない。このことは第13章でモナドを勉強すれば理解できる。
let
<-でIOアクションの値を束縛できたのと逆に、純粋な値をIOブロックの中で束縛するにはletを使う必要がある。
main = do firstName <- getLine lastName <- getLine let bigFirstName = map to Upper firstName bigLastName = map to Upper lastName putStrLn $ bigFirstName ++ bigLastName
return
returnは何もしないIOを作るのに使われる。また他の言語のreturnと違って実行してもプログラムは終了されずに続行される。以下はthenとelseの型を揃えるのに使われる例。
main = do line <- getLine if null line then return () else do putStrLn $ reversewords line main {- elseの部分はthenとelseの型が揃っていることを強調するために、 else (do putStrLn $ reversewords line main) と書くことも出来る。 -} reversewords :: String -> String reversewords = unwords . map reverse . words
実際はreturnは純粋な値をIOに変換するためにある関数で、
-- 等価 a <- return "hell" let a = "hell"
のようにも使えるが普通この用途ではletを使う。
便利な関数たち
- putStr: putStrLnと違って文末に改行を出力しない。
- putChar: 1文字(Char)を出力する。文末に改行を出力しない。
- print: String以外の値を表示するのに使われる。基本的には
putStrLn . show
と同じでShow型のインスタンスなら何でも表示できる。 - when: Control.Monadをインポートして利用する。条件分岐でTrueの場合にしかIOアクションがなくても良い場合に便利な関数。
import Control.Monad main = do when (input == "SWORDFISH") $ do putStrLn input -- 等価 main = do if (input == "SWORDFISH") then putStrLn input else return ()
- sequence: IOアクションのリストをとってまとめたIOアクションを1つ返す。
> rs <- sequence [getLine, getLine] > print rs ["something", "otherthing"] > sequence $ map print [1,2,3] 1 2 3 [(),(),()]
2つ目の例の最後の[(),(),()]はsequenceが表示させたものではなく、ghciがIOアクションを表示した際に帰ってくる型が()以外だとその型を表示するようになっているため。
- mapM、mapM_: IOアクションをmapするのに使う。(sequence不要)
-- 等価 > sequence $ map print [1,2] > mapM print [1,2] 1 2 [(),()] -- IOアクションをまとめて評価する? > mapM_ print [1,2] 1 2
- forever: Control.Monadをインポートして利用する。IOアクションを引数にとってそのアクションを繰り返す。
main = forever $ do...
- forM: Control.Monadをインポートして利用する。forMはリストの要素それぞれについてIOアクションを起こし、その結果をまとめたものIOアクションを返す。mapMと引数の並びが違う。
import Control.Monad main = do colors <- forM [1,2,3,4] (\a -> do putStrLn <- show a color <- getLine return color) mapM_ putStrLn colors
プログラム中のある部分だけでアクションを並べて実行したいときにdoと一緒に使う。