読者です 読者をやめる 読者になる 読者になる

LYH 8.3 I/O アクションどうしをまとめる

8.3 I/O アクションどうしをまとめる

main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn $ "Zis is your future: " ++ tellFortune name

8.1と説明が重複するところがあるが、もう一度書く。

上のコードでは、getLineが副作用を扱っているものの、Stringを束縛したnameが関わるいかなる関数においてもI/Oについての情報を知る必要はない。上のtellFortuneという関数はString -> Stringという型シグネチャを持っているものとする。確かにI/Oの影響が及ぶ箇所はない。副作用が発生する部分は、getLineの中だけに留まっている。このIOアクションによって生成された結果が<-によって取り出されてnameに束縛されてからは、副作用のない世界でnameが処理されるようになる。

IOアクションは、実行されるまで不活性なままである。Haskellではmainという、ただ一つのIOアクションが実行される。mainが1つのIOアクションのみで定義されるなら、

main = some IO action

となるし、doブロックでまとめれば

main = do
    (IO action 1)
    (IO action 2)
    ...
    do
        (IO action x)
        (IO action y)
        ...

と記述することも出来る。

また、main以外でも、I/Oアクションが実行される場合がある。GHCiにI/Oアクションを入力してENTERキーを押した時である。

ghci> putStrLn "HEEY"
HEEY

以下のようなコードは書けるだろうか。

nameTag = "Hello, my name is " ++ getLine

型を考えてみよう。"Hello, my name is "の型は、String すなわち [Char] であるが、getLineの型はIO Stringである。 CharのリストとIO Stringとでは型が違うので、当然、リストの連結を行うことは出来ない。よって、このコードは間違いである。

I/Oアクションはどれも結果を生成する。

main = do
    foo <- putStrLn "Hello"
    name <- getLine
    putStrLn ("Hey " ++ name ++ "!")

上のコードは間違いではないが、putStrLn "Hello"の型はIO ()であるため、fooに束縛した値は()であり、意味がない。 また、doブロックの最後の行は束縛を書くことは出来ない。この理由は「13章モナドがいっぱい」で分かるらしい。

以下の文は、getLineをmyGetLineに変えただけで、決してgetLineからStringを取り出してmyGetLineに束縛したものではない。

let myGetLine = getLine

無意味なコードだが、上の文を書いたあとには以下のような記述が可能となる。

name <- myGetLine

8.3 確認問題

  1. 複数のI/Oアクションを糊付けして1つにするには何を用いればよいか。
  2. "Hello, what's your name?"と一行に出力し、続いて文字列として名前を読み取らせ、最後に、"Hey (名前), you rock!"と一行に出力せよ。
  3. 2においてdo構文でまとめられたI/Oアクションの型は何か。
  4. 3の型になる理由を答えよ。
  5. mainの型シグネチャはどんな形をしているか答えよ。
  6. getLineの型を答えよ。
  7. putStrLnの型を答えよ。
  8. doブロック内で、getLineをmyGetLineという名前で使えるようにするにはどうすればよいか。

8.3 解答

  1. do構文
main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")
  1. IO ()
  2. do構文の中の最後のIOアクションであるputStrLnの戻り値の型がIO ()であるため。
  3. main :: IO 具体型
  4. getLine :: IO String getLineはStringを生成するI/Oアクション
  5. putStrLn :: String -> IO ()
  6. let myGetLine = getLine