Haskellで動的スコープ

GHCでは"-XImplicitParams"というオプションで暗黙的なパラメータというのが使えるようになる。こいつがどうやら動的スコープみたいなものらしい。使い方はタイプクラスの指定のようにすればよい。

sort :: (?cmp :: a -> a -> Bool) => [a] -> [a]

(?cmp :: a -> a -> Bool)は「?cmpは動的に束縛された変数でa -> a-> Boolという型を持つ」という意味。?cmpは変数名なことに注意。

len1 :: [a] -> Int
len1 xs = let ?acc = 0 in len_acc1 xs

len_acc1 [] = ?acc
len_acc1 (x:xs) = let ?acc = ?acc + (1::Int) in len_acc1 xs

------------

len2 :: [a] -> Int
len2 xs = let ?acc = 0 in len_acc2 xs

len_acc2 :: (?acc :: Int) => [a] -> Int
len_acc2 [] = ?acc
len_acc2 (x:xs) = let ?acc = ?acc + (1::Int) in len_acc2 xs

GHCユーザーズガイドの例。len1とlen2の違いはlen_acc2は明示的に型を書いているだけだが,実行結果が異なる。len1は常に0を返す。len2はリストの長さを返す。len_acc1はMonomorphicのため再帰呼出しに暗黙的なパラメータが渡されない。len_acc1がMonomorphicなのは自身の右辺で呼んでいる自分自身によって型が制限されてしまうため。それに対してlen_acc2はPolymorphicのため渡される。len_acc2がPolymorphicなのは型シグネチャがあるため。id:cadr:20090902を参照。
ちなみに,len_acc1の型推論の結果は下記のようになり表記上はlen_acc2と同じ。ややこしい。なので,len_acc1のようなことをやりたい場合は型シグネチャを書くことはできない。たぶん。

> :t len_acc1
len_acc1 :: (?acc::Int) => [t] -> Int

len_acc1の方がいまいちわからない。len_acc1の?accはどういう扱いになるのか?len1の?accを参照しているような気がする。

len1' :: [a] -> Int
len1' xs = let ?acc = 10 in len_acc1 xs

というlen1'を追加して,(len1' "hoge")を求めると10と返ってくることを考えると,len_acc1の?accは動的スコープに感じられる。しかし,そうなるとlen_acc1の再帰呼出しだってそうなってlen_acc2と同様の動きするような気がする。これは,len_acc1のletの束縛で出てくる左辺の?accと右辺の?accは実体が違うからと思われる。束縛変数は名前を変えても動作は変わらないので(α可換?),len_acc1は次のように変更しても良い。

len_acc1 [] = ?acc
len_acc1 (x:xs) = let ?hoge = ?acc + (1::Int) in len_acc1 xs

これなら,?accが動的スコープでありlen1の返す値が常に0であるのが一目瞭然。len_acc2のlet式の束縛変数?accは名前を変えることができない。let式本体のlen_acc2の呼出しは暗黙の変数として?accを持つが,この名前を変更するすべがないので。
ちなみに,?付き変数を束縛するときのletはSchemeで言うところのletと同じ。通常,HaskellのletはSchemeで言うところのletrecにあたる。

f :: Int -> Int
f v = let ?x = 0 in
      let y = ?x + v in
      let ?x = 5 in
      y

GHCユーザーズガイドの例。(f 9)の結果は9。というのもMonomorphism Restrictionのためyが一般化されずyの型が(?x :: Int) => IntではなくIntになるため。下記のように型シグネチャを与えてあげれば(f 9)の結果は14に変わる。id:cadr:20090823を参照。

f :: Int -> Int
f v = let ?x = 0 in
      let {y :: (?x :: Int) => Int ; y = ?x + v} in
      let ?x = 5 in
      y