Namaran

Code daily. Without assist.

2026-08-27

Counter.hs

import Data.IORef
import Control.Monad (replicateM_)

main :: IO ()
main = do
  ref <- newIORef (0 :: Int)
  replicateM_ 1000000 (modifyIORef ref (+1))
  total <- readIORef ref
  print total

このコードは意味的には正しそうに見えますが、実行すると極端に遅くなったりスタックオーバーフローでクラッシュしたりします。何が問題で、どう直せばよいでしょうか?

Answer

modifyIORefは中身を遅延したまま更新するため、refの中身は実際には計算されず、(((0+1)+1)+1)...+1という巨大なサンク(thunk)の連鎖として積み上がっていきます。最後にreadIORefで取り出してprintが評価しようとした瞬間、この深い連鎖を一気に評価することになり、スタックオーバーフローや大きなメモリ消費につながります。修正は、正格版のmodifyIORef'に変えることです — 各ステップで即座に値を評価するので、サンクが蓄積しません:

replicateM_ 1000000 (modifyIORef' ref (+1))