これはHaskellスペースリーク Advent Calendar 2015の17日目の記事です。
:sprint と MonomorphismRestriction
サンクのリークを防ぐには、どの式がサンクかを理解する必要がある。そのために便利なのが、GHCiの:sprint コマンドだ。MonomorphismRestriction 拡張がデフォルトで有効だった GHCi 7.6 以前は、このコマンドは直感的に動く。
> let x = 1 + 2 > let y = (x,x) > :sprint x x = _ > :sprint y y = (_,_) > print x 3 > :sprint y y = (3,3)
GHCi 7.8 以降では、MonomorphismRestriction拡張がデフォルトで無効になった。単に式を評価する使い方なら、こちらの方が便利だ。しかし、:sprintは挙動がおかしくなってしまう。
> let x = 1 + 2 > let y = (x,x) > :sprint x x = _ > :sprint y y = _ {- あれれ? -}
MonomorphismRestriction を有効にすれば、:sprintは期待通りに動く。
> :set -XMonomorphismRestriction > let x = 1 + 2 > let y = (x,x) > :sprint x x = _ > :sprint y y = (_,_)
問題は型が決まらないことなので、明示的に指定してもよい。
> let x = 1 + 2 :: Int > let y = (x,x) > :sprint x x = _ > :sprint y y = (_,_)
unbox-small-strict-fieldsフラグ
Haskellでは、基本的にデータは参照で指される。GHC 7.6 までは、即値を使うには、"!" と UNPACK プラグマの両方を指定する必要があった。
data Foo = Foo { bar :: {-# UNPACK #-} !Int , baz :: {-# UNPACK #-} !Int }
これはあまりにもダサい。GHC 7.8 以降では、unbox-small-strict-fieldsフラグがデフォルトで有効になっているので、即値として扱える正格なフィールドは即値となる。
data Foo = Foo { bar :: !Int , baz :: !Int }
マニアックなことを言うと、たとえば Int は以下のように定義される。
data Int = Int Int#
Int が参照で、Int# が即値を表す。詳しくはUnboxed values as first class citizensを参照のこと。
Strict プラグマと StrictData プラグマ
GHC 8.0 以降では、Strict プラグマと StrictData プラグマが使えるようになる。"!" もダサいという人にはオススメ。
Weak ThreadId
スレッドリークの記事で、Weak ThreadId を使えばスレッドがリークしないと書いたが、実はリークしていることが判明した。
- ThreadId を保持するとスレッドのスタックもスレッドに関するデータもリークする
- Weak ThreadId を保持するとスレッドのスタックは開放されるが、スレッドに関するデータはリークする
はっきり言ってGHCのバグ。対策として、
- Weak ThreadId の利用を止めて素直に ThreadId を使う
- ThreadId を保持する IO アクションを適宜 return () で上書きする
のようにするよい。詳しくは、Weak ThreadId still leaks threadsを読んで欲しい。