60137


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

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

(A)

[code]

    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
    
[/code]

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

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

[code]

    import System.IO.Unsafe
    
    unsafeVal x = unsafePerformIO $ do
        print x
        return x
    n = 10
    
    main = print $ (unsafeVal n) * (unsafeVal n)
    -- output : 10\n100\n
    
[/code]

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

[code]

    import System.IO.Unsafe
    
    unsafeVal x = unsafePerformIO $ do
        print x
        return x
    
    unsafeVal10 = unsafeVal 10
    
    main = print $ unsafeVal10 * unsafeVal10
    -- output : 10\n100\n
    
[/code]

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

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

[code]

    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
    
[/code]

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

[code]

    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
    
[/code]

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

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

* * *

EDIT:

  * [@DariusJAHANDARIE](https://ja.stackoverflow.com/users/2860/darius-jahandarie) さんの仰るとおり（ありがとうございます），もともとGHCのCommon Subexpression Eliminationはいつでも働くわけではないようで， [HaskellWiki](https://www.haskell.org/haskellwiki/GHC_optimisations#Common_subexpression_elimination) にも

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

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

  * [@snak](https://ja.stackoverflow.com/users/3066/snak) さんが GHC 7.8.3 でお試しくださったところすべて２回目の`10`が表示されなかったとのことで，元の記事でも同じ挙動が期待されていますから，バージョン（あるいは環境…？）依存なのかもしれません．どなたか他の環境でもお試しいただければ，コメントにお寄せください．

* * *

再EDIT:

7.6.3 と 7.8.3 でたしかに挙動が違うようです
([[1]](http://melpon.org/wandbox/permlink/YWZ1n9Ug6pz111TT)
[[2]](http://melpon.org/wandbox/permlink/6k2rwzs3cUQXyhfN) : リンク先いずれもwandbox)．


CC BY-SA 3.0
2015-01-06T05:33:23.303
3167
2019-10-31T04:35:55.170
2017-05-23T12:38:55.250
-1
2901
question
haskell
Haskell の数値リテラルに対するメモ化


コメント欄で随分前に解決済みのようですので、回答として転載しておきます。

> 何ででしょうね…この最適化は「共通部分式の削除」（CSE: Common Subexpression
> Elimination）と言いますが、よく分からない理由で、最近のGHCでは全く動作させません。GHCは元々最も一般のCSEはしていませんでしたが、簡単なCSEはしていました。（正確に言うと、thunkを作らない場合。例：
> `let x = trace "test" 1 in x + x + trace "test" 1` ⇒ `let x = trace "test" 1
> in x + x + x`。）でも今はその簡単なCSEでも起こらなさそうです。いつからこうなったかは分かりません。 – [Darius
> JAHANDARIE](https://ja.stackoverflow.com/users/2860/darius-jahandarie)
> 15年1月6日 7:27


CC BY-SA 4.0
2019-10-30T13:46:58.127
60137
2019-10-31T04:35:55.170
2019-10-31T04:35:55.170
19110
17181
3167
answer