Haskellとglobal

こちらhttp://www.haskell.org/haskellwiki/Research_papers/Functional_pearls で"Global Variables in Haskell"という論文を発見…しかし、functional pearlsは色々とためになるものがあります…

そういえば、大域変数はHaskellでは大変だったのか?まぁ、定数であればまったく問題ないわけですが、何せ純粋関数型なので、Immutableな大域変数は再帰呼び出しに混ぜ込むこともできないし、変化のさせようがない…ということで、IORefやMVarなどのMutableな型を利用することが前提になってきます。

なんですが、IORefを使えばそれで全てがハッピーかといえば、そうではないわけです。IORefを使えば中身を読んだり書き換えたりすることができるのですが、IORrefはnewIORef :: a -> IO (IORef a)を使ってアロケートしなくてはいけないものなので、普通の定数のようにグローバルスコープにいきなり宣言するわけにはいかないのです…ということは、おのずと関数の中でnewIORefを行って、それを変数に突っ込んでおくわけですが、そうすると、その変数は関数のスコープに閉じ込められてしまう。

この論文では、HaskellでどうやってMutableな大域変数を実現するかというお話…ありえる方法は3つ。

一つ目はunsafePerformIOを使うやり方。これを使えば、
global = unsafePerformIO newIORef "initial value"

とやって、IORefな値を持った大域変数が手に入る…でも、unsafeなので却下。

2つ目はStateモナドのように大域変数をモナドの中に織り込んで、さまざまな場所からアクセスできる用とする方法。一番Haskellとはなじみの良い方法ですが、呼び出したい関数全てがこの新しいモナドを受け入れられないといけないので、以前にWin32のサンプルで取り上げたようなdispatchMessage :: LPMSG -> IO LONGなんていうWin32側からHaskell側のコールバックを呼び出してくるような関数では何もできない…

そして、真打3つ目はimplicit parametersというもの…要は関数の宣言に指定しなくても勝手に渡っているパラメタ…まぁ、unsafePerformIOよりは醜くないけれど、どうなのよという感じ…
 implicit parameterは全て変数名の頭に?がつく…そして、使い方はこんな感じ…

main = let ?glob = 4 
	in
	print $ map (add') [1, 2, 3, 4]
	
add' = add

add :: (?glob :: Int) => Int -> Int
add x = x + ?glob

=> [6, 7, 8, 9]

ということで、注目すべきはmainの一行目とaddの宣言部…addのタイプシグネチャのところでこの関数は?globというimplicit パラメタをとりますよとやる…そうすると、関数の中で使えるようになる…

そして、実際に?globに値を代入するのはletやwhereを使う…そうすると、add'やaddの呼び出し部では?globを渡さなくても勝手に渡ってくれるわけです…これならば一つの関数以上のスコープで同じ変数を共有できますね…
もう一つポイントとしてあるのはこのサンプルではadd'をmapに渡してmapの中からadd'を呼び出してもらっていること…これが動くということはimplicit parameterをまったくケアしていないライブラリ関数などとも普通に使えそうだということで助かります…

一ついやなことは、このサンプル、add'にimplicit parameterを省いた型宣言をつけるとコンパイルがこけることです。あとは-fno-monomorphism-restrictionをつけないと型推論がimplicit parameterをうまく解釈してくれないことがあること…

なんとも苦しい気がします…

今日のところはこの辺で、
ではでは。