言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Haskell の、IO に関する実験

単純な入出力の実験をする。名前を入力すると挨拶する(この文書は .lhs と
して保存すると実行出来る)。

> import Random -- この行は後で使う。
> sayHello :: IO ()
> sayHello = do putStr "What is your name? :"
>               name <- getLine
>               putStr ("Hello " ++ name ++ "!")

一見なんとも無い書き方に見えるだけに、Haskell での入出力は手ごわい。入
出力を含むコードは含まないコードと意味合いが全然違ってくる。実は 
Haskell では関数を実行中に入出力を行う事が出来ない。その代わり、入出力
を行うためのプログラムを返す関数を作る。例えば、"hello" は文字列を返す
式だが、putStr "hello" は文字列を表示したいという気持ちを表す式だ。こ
のような式を IO 型という。putStr の型は String -> IO () 型で、受け取っ
た文字列を表示したいという気持ちだけを表す。Haskell インタプリタである 
hugs は、評価結果として IO () 型を受け取ると、気持ちだけを受け取るわけ
には行かなくてさらに実行する事になっている。

他の行を見てみる。getLine の型は IO String 型で、これは文字列を入力し
たいなあという気持ちを表す。<- は代入のように見えるが、右側の入力を左
に代入したいんだけどという気持ち表す。

なぜこんなに回りくどい事をやってるかと言うと Haskell では、引数が同じ
なら結果は同じ(参照透過性)だと言う約束がある。普通のプログラミング言語
では、引数が同じでもユーザからの入力なんかによって振る舞いが変わるのだ
が、関数型言語ではこの変化を嫌う。しかし、プログラムには入出力が付き物
なので、Haskell は入出力を「入出力をする気持ち」に纏めて計算をする。こ
れにより、気持ちの部分とそうでない部分が明確に分離する。

- 普通の言語 : プログラムを書く -> 評価実行する
- Haskell : プログラムを書く -> 評価(簡約)する -> 実行する

Squeak でも、プログラム自体をプログラムで書くというのは良くある技なの
で、同じような事は出来るかも知れない。

次にもうちょっとましな例を示す。乱数を使った足し算テスト。ここでは 
Random モジュールを使っている。rollDice はライブラリにあった例をそのま
ま使っているが、これは IO Int 型、つまり整数を渡したいという気持ちを表
す。他に注意がいるのは read 関数かな。これは、文字列をオブジェクトに変
換するのだが、文脈から数字にしたいのだろうと推測して数字に変換してくれ
る。このあたりは Haskell の型システムの強力な部分だ。

> rollDice :: IO Int
> rollDice = getStdRandom (randomR (1,9))

> exam :: IO ()
> exam = do q1 <- rollDice
>           q2 <- rollDice
>           putStr ((show q1) ++ " + " ++ (show q2) ++ " = ? ")
>           ans <- getLine
>           if read ans == q1 + q2
>               then putStr "Correct"
>               else putStr "Wrong"