あどけない話

インターネットに関する技術的な話など

遅延評価だけだと出力の順番が定まらない例

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モナドを説明してくれると、納得できるのになぁ。