Haskell に関してよく見かける説明は、おおむね次のような感じだ。「遅延評価では、その値が必要になったときに初めて評価されるので、順番が大切な入出力とは相性が悪い。」
Haskellの入出力は、基本的にIOモナドを使用しないと扱えない。IOモナドは、入出力の順番を制御してくれる。だから、遅延評価は入出力と相性が悪いと言われても、実際相性の悪い例題は示せないので、納得できない。
この問題を長い間考えていたが、昨日ひらめいた。Haskellでは純粋な関数にIOを忍び込ませることは、基本的にはできないけれど、裏技がある。Debug.Traceで定義されている trace がその掟を破る。
trace は第一引数を出力し、第二引数を返す関数である。出力するにも関わらず、関数の型にはIOは現れない。
trace :: String -> a -> a trace string expr = unsafePerformIO $ do putTraceMsg string return expr
この関数を使いやすくするために、debugPrint という関数を定義してみよう。
import Debug.Trace debugPrint :: (Show a) => a -> a debugPrint x = trace (show x) x
この関数は、引数を一つ取り、それを文字列に変換して表示した後、その引数そのものを返す。print デバッグにもってこいだと分かるだろう。
そして、いよいよ遅延評価で出力の順番がおかしくなる例を書いてみる。
main = let a = debugPrint 1 b = debugPrint 2 in print (b + a)
遅延評価を知らなければ、このプログラムは以下のように出力すると思うだろう。
1 2 3
しかし、実際に走らせてみると、以下のような結果になる。
2 1 3
なぜなら、b の方が、a よりも先に必要になるからだ。
こういう例題を示してから、IOモナドを説明してくれると、納得できるのになぁ。