Haskell の入出力

Haskell の入出力に関する関数は副作用を伴うため、Haskell の他の純粋関数とは性質が違う。

例えば getLine という関数は、端末から文字列を読み込むが、読み込む文字列が異なると、戻り値も異なる。つまり同じ getLine という関数が異なる値を持つ事になる。これは、同じ関数に同じ引数が与えられたら必ず同じ値が得られるという純粋関数の大前提を満たさない。

Prelude> getLine
hello
"hello"
Prelude> getLine
world
"world"

そこで、Haskell では getLine の戻り値をモナド IO でラッピングして IO "hello" のようにして戻す。また、 IO "hello" からパラメータをパターンマッチで取り出せないようにしてある。このようにすれば、たとえ getLine が値 IO "hello" を返したとしてもそれを純粋関数では利用できないので、純粋関数の世界を汚染することはない。このような IO a 型の値はアクションと呼ばれている。

このように、Haskell の入出力を担当するIOモナドの関数は必ず IO a 型のアクションという値を返す。アクションのコンテナの値 a はパターンマッチでは取り出せないので、純粋関数からは利用することができない。そのため、副作用を発生するIO関係の処理を、本体の関数とは隔離できる。

Haskell は説明は難しいが、使うと簡単なのでまず使ってみよう。次の関数 getLine は端末から文字列を取得する関数だ。

Prelude> getLine
hello
"hello"

また、putStrLn は文字列を端末に表示する関数だ。

Prelude> putStrLn "hello"
hello

do 記法を使うと getLine と putStrLn を連携させる事ができる。

Prelude> do cs <- getLine; putStrLn cs
hello
hello

ところが、次のプログラムは端末から入力した文字列を "hello" と比較しようとしたが文法エラーになってしまった。

Prelude> do cs <- getLine; cs == "hello"

:7:19:
Couldn't match expected type `IO b0' with actual type `Bool'
In a stmt of a 'do' block: cs == "hello"
In the expression:
do { cs <- getLine;
cs == "hello" }
In an equation for `it':
it
= do { cs <- getLine;
cs == "hello" }

これは cs == "hello" の値が Bool 型で IO Bool 型になっていないからだ。このようなときはこの値を return 関数の引数にして IO a 型にラッピングしてやるとうまくいく。

Prelude> do cs <- getLine; return (cs == "hello")
hello
True

つまり、入出力関数は、戻り値はすべてIO a型のアクションでないといけない。入出力関数の戻り値 IO a 型のパラメータを純粋関数から取り出すことはできないので、この値が純粋関数に影響を与えることはなく、入出力の値は全てIOモナドの世界に閉じ込められてしまう。

入出力関数の値は必ず IO a 型であることというルールが分かると、次のようにコンソールから対話するプログラムが書けるようになる。

Prelude> do
Prelude|   putStr "name: "
Prelude|   name <- getLine
Prelude|   putStrLn ("Hello, " ++ name)
Prelude|
name: Dolly
Hello, Dolly

入出力関係は、入出力で発生する値をIOモナドの世界に閉じ込めることさえ注意していればそう怖いものでもないようだ。多分エラーのほとんどが戻り値を IO 型のアクションにしなかったために起こるのだろうから。

入出力関係のプログラミングについては、Haskell であっても、プログラミングのスタイルが手続き型と変わらなくなる。cs <- getLIne のようなアクションは、関数が戻す値は不定だ。しかし、関数言語の建前としては、入力に対しては常に同じ出力が戻り値にならなければならない。また、do 記法で表されるプログラムは実行順序が大切だ。do cs <- getLine; putStrLn cs のようなプログラムは、getLine と putStrLn の実行順序を変えられない。これも、出力が関数の評価順に関係しないという関数プログラミングの原則を破っている。

入出力のプログラムのこれらの問題のため、入出力関係の関数から発生する値をIOモナドに押し込め、純粋関数のプログラムの世界からはアクセスできないし、純粋関数の世界への影響もできないようにして、これらを隔離したとも考えられる。
[PR]
by tnomura9 | 2009-08-09 22:28 | Haskell | Comments(0)
<< Haskell でユーザ定義の... Haskell のすすめ >>