hGetContents と hClose

hGetContents に渡したハンドラを hClose すると、hGetContents の結果が空文字列になる。

import IO (openFile, IOMode(ReadMode), hGetContents, hClose, bracket)

f1 = do h <- openFile "hoge.txt" ReadMode
        s <- hGetContents h
        return s

f2 = bracket (openFile "hoge.txt" ReadMode)
             (hClose)
             (hGetContents)

main = do run "f1" f1
          run "f2" f2
  where run name act = do putStrLn name
                          putStrLn =<< act
                          putStrLn ""

f1 では hoge.txt の内容が得られて、f2 では空文字列が得られる。

Haskell でネットワーク - zyxwvの日記

で紹介されているように、hGetContents の結果文字列が評価されるまで hGetContents の IO 処理は遅延されるわけだから、その前に hClose すると空になっちゃうよという話。

じゃあこれは?

import IO (openFile, IOMode(ReadMode), hGetContents, hClose, bracket)
import Control.Exception (evaluate)

f3 = bracket (openFile "hoge.txt" ReadMode)
             (hClose)
             (\h -> hGetContents h >>= evaluate)

f4 = bracket (openFile "hoge.txt" ReadMode)
             (hClose)
             (\h -> hGetContents h >>= return . length)

f5 = bracket (openFile "hoge.txt" ReadMode)
             (hClose)
             (\h -> hGetContents h >>= print . length)

f3 と f5 は期待した値が得られた。f4 は f2 と同じ話で、評価する前に閉じるからアウト。

evaluate は値を正格評価しつつ IO モナドでくるむ。seq と return をあわせた感じ (実装を見てみたらもっと複雑なことしてた)。