かなり古い記事ですが,本物のプログラマはHaskellを使う - 第31回 禁断の機能「unsafePerformIO」の深淵:ITproという記事を見つけ,メモ化についての記述に興味を持ち記載のコードを試したところ少し挙動が違って,単なる数値リテラルが(という言い方は不適切のような気がしますが)メモ化されないように見えたので,それについての質問です.

コンパイラは ghc の 7.6.3, ghc $1 -O2 でコンパイルした結果です.

(A)

import qualified System.IO.Unsafe as U

-- U.unsafePerformIO :: IO a -> a
unsafeVal x = U.unsafePerformIO $ do
    print x
    return x

main = print $ unsafeVal 10 * unsafeVal 10
-- output : 10\n10\n100\n

-O2 でコンパイルしても10 が2回印字されます(これは上掲の記事での記述とは異なる結果).

(B) 10 を何かに bind して使いまわす

import System.IO.Unsafe

unsafeVal x = unsafePerformIO $ do
    print x
    return x
n = 10

main = print $ (unsafeVal n) * (unsafeVal n)
-- output : 10\n100\n

(C) 関数適用の結果を bind しておく

import System.IO.Unsafe

unsafeVal x = unsafePerformIO $ do
    print x
    return x

unsafeVal10 = unsafeVal 10

main = print $ unsafeVal10 * unsafeVal10
-- output : 10\n100\n

(B),(C) いずれも -O2 でコンパイルすると2回目の10は印字されません(予期される通りの結果).

(D) : 10 に型指定を加えたもの

import System.IO.Unsafe

unsafeVal x = unsafePerformIO $ do
    print x
    return x

main = print $ (unsafeVal (10::Int)) * (unsafeVal (10::Int))
-- output : 10\n10\n100\n

(E) unsafeValInt を明示的に type signature として追加したもの

import System.IO.Unsafe

unsafeVal :: Int -> Int
unsafeVal x = unsafePerformIO $ do
    print x
    return x

main = print $ unsafeVal 10 * unsafeVal 10
-- output : 10\n10\n100

(D), (E) は 型クラスによる signature がメモ化を阻害するというような話を読んだことがあった(cf. haskell - Why ghc changes the evaluation way due to the optimisation flag? - Stack Overflow)ので試したものですが,これでも効果はないように見えます.

  • 上記のコードで, 10 が2回印字される例ではメモ化が起こっていないという理解でよいでしょうか?
  • その理解で正しい場合,なぜ (B) や (C) のようにしないとメモ化が起こらないのでしょうか?
  • ひょっとするとこれは unsafePerformIO に関連して発生していることでしょうか,そうでない一般の条件でも同様でしょうか?

EDIT:

  • @DariusJAHANDARIE さんの仰るとおり(ありがとうございます),もともとGHCのCommon Subexpression Eliminationはいつでも働くわけではないようで, HaskellWiki にも

GHC doesn't actually perform CSE as often as you might expect.

とあります.ただこの場合にCSEが起こることが期待されるのかについては(期待されるようですが)特段の資料は見つけられていません.

  • @snak さんが GHC 7.8.3 でお試しくださったところすべて2回目の10が表示されなかったとのことで,元の記事でも同じ挙動が期待されていますから,バージョン(あるいは環境…?)依存なのかもしれません.どなたか他の環境でもお試しいただければ,コメントにお寄せください.

再EDIT:

7.6.3 と 7.8.3 でたしかに挙動が違うようです ([1] [2] : リンク先いずれもwandbox).