暗黙のパラメータ
ImplicitParams
6.8.1以降
暗黙のパラメータを要求する関数の定義を許す.
暗黙のパラメータはルイス2000で説明されているように実装され,ImplicitParams拡張で有効になる(以下のほとんどの,まだやや不完全なドキュメントは,Jeff Lewisによるものである). ルイス2000 「暗黙のパラメータ:静的型による動的スコープ」, J Lewis, MB Shields, E Meijer, J Launchbury, 第27回プログラミング言語の原理に関するACMシンポジウム(POPL'00), ボストン, 2000年1月. 変数が関数の呼び出し元の文脈(calling context)によって束縛されるとき,その変数は動的に束縛されると言い,呼び出し先の文脈(callee context)によって束縛されるとき,その変数は静的に束縛されると言う.Haskellでは,すべての変数は静的に束縛されている.変数の動的束縛はLispに遡る概念だが,後にSchemeのようなより近代的な形では廃棄された.動的束縛は型なし言語では非常に混乱を招きうるものである.残念ながら,型付き言語,特にHaskellのようなHindley-Milner型付き言語は,変数の静的スコープのみをサポートする.
しかし,Haskellの型クラスシステムを単純に拡張することで,動的束縛をサポートできる.基本的には,動的束縛変数の使用を型の制約として表現する.これらの制約は,(?x::t') => tという形式の型になり,これは「この関数は、t'型の動的に束縛された変数?xを使用する」という意味である.たとえば,次の例は,cmpという名前の比較関数によって暗黙的にパラメータ化されているソート関数の型を表している.
code: ip1.hs
sort :: (?cmp :: a -> a -> Bool) => a -> a 動的束縛制約は,型クラスシステムにおける新しい述語の形式にすぎない.
暗黙のパラメータは特殊な形式?xを使った式の中に現れる.ここでxは任意の有効な識別子である(例えば、ord ?xは有効な式である).この構文を使用すると,式の型に新しい動的束縛制約も導入される.たとえば,次の定義は,明示的にパラメータ化されたsortBy関数を用いて,暗黙的にパラメータ化されたsort関数をどうやって定義できるかを示している.
code: ip2.hs
sortBy :: (a -> a -> Bool) -> a -> a sort :: (?cmp :: a -> a -> Bool) => a -> a sort = sortBy ?cmp
暗黙のパラメータでの型制約
動的束縛制約は,自動的に伝播されるという点で他の型クラス制約とまったく同じように動作する.したがって,関数が使用されると,その暗黙のパラメータはそれを呼び出した関数によって継承される.例えば,私たちのsort関数はリストの中で最小の値を選ぶために使われるかもしれない.
code: ip3.hs
least :: (?cmp :: a -> a -> Bool) => a -> a least xs = head (sort xs)
(ユーザーが)全く手間をかけずとも,?cmpパラメータはleastのパラメータにもなるように伝播される.明示されたパラメータの場合,デフォルトではパラメータは常に明示的に伝播される必要がある.暗黙のパラメータの場合,デフォルトでは常にそれらが伝播される.
暗黙のパラメータ型制約は,「特定の暗黙のパラメータを使うならすべて同じ型でなければならない」という点で他の型クラス制約と異なる.これは (?x, ?x)の型は(?x::a) => (a,a)であって、(?x::a, ?x::b) => (a, b)ではない(仮に型クラス制約と同様の仕様だったら(?x::a, ?x::b) => (a, b)になるだろうが,そうではない)ということを意味する.
クラスまたはインスタンスの宣言のコンテキストでは,暗黙のパラメータを持つことはできない.たとえば,以下の宣言はどちらも違法である.
code: ip4.hs
class (?x::Int) => C a where ...
instance (?x::a) => Foo a where ... 理由
どの暗黙のパラメータを選択するかは,関数を呼び出す場所によって異なる.しかし,インスタンス宣言の「呼び出し」は,コンパイラによって舞台裏で行われるため,それがどこで行われるのかを正確に把握するのは困難である.最も簡単なのは,(訳注:このような)問題のある型を違法とすることだ.(訳注:故に,このような型クラスやインスタンス宣言のコンテキストでの暗黙のパラメータの使用は禁じられている.)
暗黙のパラメータ制約はあいまいさを引き起こさない.たとえば,
code: ip5.hs
f :: (?x :: a) => Int -> Int f n = n + length ?x
g :: (Read a, Show a) => String -> String
g s = show (read s)
ここで、gはあいまいな型を持ち,拒否されるが,fは問題ない.fの呼び出し場所での?xの束縛にはあいまい性が全く無く,型aを決定するからだ.
暗黙のパラメータの束縛
暗黙のパラメータは,標準のletやwhereを用いた束縛の形式を用いて束縛される.たとえば,関数cmpを束縛してmin関数 を定義する.
code: ip6.hs
min = let ?cmp = (<=) in least
暗黙パラメータの束縛群は,トップレベルを除いて,通常のHaskellの束縛群を書けるところにならどこにでも書ける.つまり,let(リスト内包表記,do表記,パターンガードを含む),またはwhere句で使用できる.以下の点に注意せよ.
暗黙パラメータ束縛群は,暗黙なスタイルの変数への単純な束縛の集まりでなければならない(関数スタイルの束縛や型シグネチャがあってはならない).これらの束縛は多相的で(訳注:あってはいけ)ないし,再帰的で(訳注:あってもいけ)ない.
暗黙のパラメータの束縛と通常の束縛を単一のlet式の中で混在させることはできない.代わりに,入れ子にした2つのletを使用せよ.(where句はネストできないので、whereの場合は詰みである.)
単一の束縛群に複数の暗黙のパラメータの束縛を置くことができる.しかし,それらは(通常のlet束縛と異なり)相互に再帰的なグループとして扱われない.代わりに,それらは非再帰的なグループとして扱われ,同時にすべての暗黙のパラメータを束縛する.束縛はネストされていないため,プログラムの意味を変えずに並べ替えることができる.たとえば,
code: ip7.hs
f t = let { ?x = t; ?y = ?x+(1::Int) } in ?x + ?y
?yの束縛の中で?xが使われている場所では?xの束縛は「見え」ないので,fの型は
code: ip8.hs
f :: (?x::Int) => Int -> Int
となる.
暗黙のパラメータと多相再帰
これら二つの定義について考えよう.
code: ip9.hs
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 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
2つのグループの唯一の違いは,2番目のグループlen_acc2には型シグネチャが与えられているということである.前者の場合,len_acc1はそれ自体の右辺で単相的なので,暗黙のパラメータ?accは再帰呼び出しに渡されない. 後者の場合,len_acc2は型シグネチャを持っているので、再帰呼び出しは多相的なバージョンに対して行われるが,それは暗黙のパラメーターとして?accを取る。.したがって,GHCiでは次の結果が得られる.
code: ip10
Prog> len1 "hello"
0
Prog> len2 "hello"
5
型シグネチャを追加することで結果が劇的に変わるのである.これは,かなり直感に反する現象であり,気をつけるべき現象である.
暗黙のパラメータと単相
GHCは恐ろしい単相性制限(Haskellレポートのセクション4.5.5)を暗黙のパラメータに適用する.たとえば,以下の例を考えよう.
code: ip11.hs
f :: Int -> Int
f v = let ?x = 0 in
let y = ?x + v in
let ?x = 5 in
y
yの束縛は単相性制限に該当するので,一般化はされず,ゆえにyの型は単純にIntであり,(?x::Int) => Intではない.したがって,(f 9)は結果9を返す.yに型シグネチャを追加すると,yは型(?x::Int) => Intを得るので,letの本体にあるyの登場箇所には?xの内側の束縛が見え,(f 9)は14を返す.