この節及び次の節では、型クラスに関するGHCの拡張を説明する。論文Type classes: exploring the design space (Simon Peyton Jones, Mark Jones, Erik Meijer)には、多くの背景事項が書かれている。
-XMultiParamTypeClasses
フラグが有効なら、以下のような、多引数の型クラスが許される。
class Collection c a where union :: c a -> c a -> c a ...etc.
Haskell 98では、クラス宣言の文脈(スーパークラスを導入するのはこれである)は単純でなければならない。すなわち、すべての述語が、型変数にクラスを適用した形でなければならない。-XFlexibleContexts
フラグ(7.13.2. 型シグネチャの文脈)はこの制限を外し、クラス宣言の文脈には、クラス階層に循環があってはならないという制約のみがかかるようにする。従って、以下のようなクラス宣言が認められる。
class Functor (m k) => FiniteMap m k where ... class (Monad m, Monad (t m)) => Transform t m where lift :: m a -> (t m) a
Haskell 98と同様に、クラス階層に循環があってはならない。しかし、「循環があってはならない」というのは、スーパークラスという関係についてのみである。例えば、以下は問題ない。
class C a where { op :: D b => a -> b -> b } class C a => D a where { ... }
この場合、C
はD
のスーパークラスであるが、C
のクラス演算であるop
がD
に言及するのは問題ない。(D
をC
のスーパークラスにするのは駄目である)
制約の種を追加する拡張を使うと、さらに風変りなスーパークラス定義を書くことができる。この場合、スーパークラス循環の検査はさらに緩和される。たとえばこれは問題ない。
class A cls c where meth :: cls c => c -> c class A B c => B c where
クラスC
のスーパークラス文脈が許されるのは、型シノニムをその右辺へと展開し、使われている(C
以外の)クラスをそれらのスーパークラスへと展開したときに、構文的にその文脈内にC
が現れない場合である。
Haskell 98では、クラスメソッドの型中に、クラス型変数についての制約が現れることを禁止している。次のような場合である。
class Seq s a where fromList :: [a] -> s a elem :: Eq a => a -> s a -> Bool
Haskell 98ではelem
の型は不正である。制約Eq a
がクラス型変数(この場合a
)のみを拘束しているからである。GHCはこの制限を撤廃する(-XConstrainedClassMethods
フラグ)。
Haskell 98は、クラスを宣言する際にデフォルト実装を定義することを許している。以下のように。
class Enum a where enum :: [a] enum = []
enum
メソッドの型は[a]
であり、これがデフォルトメソッドの型でもある。-XDefaultSignatures
というフラグを使うと、この制限を撤廃し、デフォルトメソッドに別の型を与えることができる。例えば、genum
というメソッドを持つGEnum
というクラスに、GHC.Generics
を使って列挙の総称的な実装を書いたなら、その総称的な実装を使うデフォルトメソッドを次のように指定することができる。
class Enum a where enum :: [a] default enum :: (Generic a, GEnum (Rep a)) => [a] enum = map to genum
default
キーワードを再利用することで、シグネチャがデフォルトメソッドにのみ適用されることを示している。Enum
クラスのインスタンスを定義する際には、enum
の元々の型である[a]
が変わらず適用される。しかし、空のインスタンスを与えた場合には、デフォルト実装であるmap to genum
が補充され、(Generic a, GEnum (Rep a)) => [a]
という型で型検査される。
我々は、GHCにおける総称プログラミングを単純にするためにデフォルトシグネチャを使っている(7.24. 総称プログラミング)。
-XNullaryTypeClasses
で有効になる。使える引数がないので、無引数の型クラスには最大でも一つのインスタンスしか存在できない。無引数の型クラスは、なんらかの前提(リーマン予想に依存するだとか)を文書化する方法として、あるいは、大域的に調整できる設定をプログラムに加えるために、使えるかもしれない。例えば、 class RiemannHypothesis where assumeRH :: a -> a -- Miller testの決定論的な版 -- 正しさは一般化リーマン予想に依存する isPrime :: RiemannHypothesis => Integer -> Bool isPrime n = assumeRH (...)
isPrime
の型シグネチャは、この関数の正しさが証明されていない予想に依存することをユーザに伝える。この関数を使う場合、ユーザは以下のようにしてこの依存を明らかにしなければならない。 instance RiemannHypothesis where assumeRH = id
関数従属は、“Type Classes with Functional Dependencies”, Mark P. Jones, In Proceedings of the 9th European Symposium on Programming, ESOP 2000, Berlin, Germany, March 2000, Springer-Verlag LNCS 1782,で述べられている通りに実装されている。
関数従属は、クラス宣言の構文中で次のように垂直線を使うことで導入される。
class (Monad m) => MonadState s m | m -> s where ... class Foo a b c | a b -> c where ...
本来もっと説明があるべきだが、(まだ)ない。必要なら文句をいってほしい。
クラス宣言では、全てのメソッドについて、そのメソッドの型中の自由変数から、クラス型変数が全て(7.13.2. 型シグネチャの文脈で言われている意味で)到達可能でなければならない。例を挙げる。
class Coll s a where empty :: s insert :: s -> a -> s
これは正しくない。empty
の型がa
に言及していないからである。関数従属を使ってこの型変数に到達できるようにすることができる。
class Coll s a | s -> a where empty :: s insert :: s -> a -> s
あるいは、次のようにColl
を書き換えても良い。
class Coll s a where empty :: s a insert :: s a -> a -> s a
a
の集積物の型((s a)
)と要素の型であるa
との間につながりを作った訳である。 これがうまく行かないこともある。そういうときは、次のようにクラスを分割することができる。
class CollE s where empty :: s class CollE s => Coll s a where insert :: s -> a -> s
なぜ関数従属が必要か、およびどのようにそれを使うか、についての以下の解説は、Hugsの利用者手引きから採られたものである。Mark Jonesが親切にも許可をくれたので、ここに(多少の改変の上で)転載する。
以下のクラスを考えてみよう。これは、何かの集まりを表す型を扱うライブラリの一部という意図である。
class Collects e ce where empty :: ce insert :: e -> ce -> ce member :: e -> ce -> Bool
型変数eは要素型を表しており、ceはコンテナの型を表している。この枠組みの下で、このクラスのインスタンスとして、リストや特性関数(どちらも、等値比較可能なあらゆる要素型の集まりを表現できる)、ビット集合(文字の集まりを表現できる)、ハッシュ表(ハッシュ関数のあるあらゆる要素型の集まりを表現できる)を持ちたいとしよう。標準的な実装の詳細を省くと、これは次のような宣言になる。
instance Eq e => Collects e [e] where ... instance Eq e => Collects e (e -> Bool) where ... instance Collects Char BitSet where ... instance (Hashable e, Collects a ce) => Collects e (Array Int ce) where ...
クラスを定義し、いくつかの有用な実装を定義したわけで、ここまでのところ順調である。残念ながら、このクラス宣言にはいくつかの重大な問題がある。第一に、empty関数の型が曖昧である。
empty :: Collects e ce => ce
「曖昧」だというのは、型変数eが=>
の左辺に現れるにもかかわらず、右辺には現れないということである。これが問題なのは、Haskellの多重定義の理論的基礎によると、曖昧な型のある項には、明確に定義された意味が与えられることを保証できないからである。
この問題を回避するだけなら、クラス宣言からemptyメンバを取り除くだけで良い。しかし、残りのメンバであるinsertとmemberも、型こそ曖昧でないが、使おうとすると問題が発生する。例えば、以下の例を考えてほしい。
f x y = insert x . insert y g = f True 'a'
これに対して、GHCは以下の型を推論する。
f :: (Collects a c, Collects b c) => a -> b -> c -> c g :: (Collects Bool c, Collects Char c) => c -> c
fではxとyの値を一つの集まりに対して順番にinsertしようとしているが、fの型は、この二つの変数が異なる型を持つことを許している。我々が 表現しようとしているのが、ただ一つの型の値のみを含む集まりだとすれば、これは明らかに不正確な型である。さらに悪いことに、gの定義は、型エラーを起こすことなくコンパイルを通過する。結果として、このコードの誤りはすぐには明らかにされない。これが発覚するのはgを使おうとしたときで、これは別のモジュールであることさえあり得る。
上記の問題点に直面して、Haskellプログラマの中には次のようなクラス宣言を使うのはどうかと考える者もいるだろう。
class Collects e c where empty :: c e insert :: e -> c e -> c e member :: e -> c e -> Bool
決定的な違いは、ここでは、集まりの型(元のクラス宣言のce)ではなく、型構築子cに関して抽象化しているという点である。集まりの型はcを使ってc eと表される。これで、上記の直接的な問題は解決された。emptyの型はCollects e c => c e
であり、これは曖昧ではない。
前節の関数fには、より正確な型が与えられる。
f :: (Collects e c) => e -> e -> c e -> c e
前節の関数gは、意図したとおり、型エラーで拒絶される。これは、fの型が、二つの引数が異なる型を持つことを許していないからである。従って、これは、多引数の型クラスが、曖昧さの問題無しに、実際的にかなりうまく行く例である。しかし欠陥もある。このCollectsクラスは、元々のクラスの意図と比べて著しく一般性に欠ける。上記のCollects
に対する四つのインスタンスのうち、このCollectsに対して動作するのはリストのインスタンス一つだけである。ある型構築子cと要素型eについてc eという形で書けるのはこれだけだからである。
より有用なCollectsクラスを得るために、Hugsでは、多引数の型クラスにおいて引数間の従属関係(依存関係)をプログラマが指定できるようにする機構が提供される。(理論的基礎や先行研究に興味のある読者向け: 従属性情報の利用は、Chen、Hudak、Oderskyの提出した「パラメータ付き型クラス」の提案を一般化した物とも捉えられるし、Mark Jonesによるqualified typeの「improvement」のためのフレームワークの特別な場合とも捉えられる。基礎となる考えは原稿[implparam]で、より理論的で抽象的な土台で考察されている。そこでは、暗黙なパラメタ化のためのシステムの一般的な設計空間の中の一点として扱われている)。抽象的な例として、次のような宣言を考えよう。
class C a b where ...
このようにすると、Cは型(aやbの種によっては型構築子)上の二項関係と捉えられる。次の例のように、クラス定義中に節を追加して、引数間の従属性に関する情報を追加することができる。
class D a b | a -> b where ... class E a b | a -> b, b -> a where ...
ここで|とwhereの間に書かれているa -> b
という記法(関数の型と混同してはならない)は、引数aが引数bをただ一つ定めるということを意味しており、例えば「aがbを決める」と読む。従ってDは単なる関係ではなく、(部分)関数である。同様に、Eの定義中の二つの従属関係から、Eが(部分的な)一対一写像を表していることがわかる。
より一般的には、従属関係はx1 .. xn -> y1 ... ym
という形をとる。ここでx1, ..., xnおよびy1, ..., yn(n>0、m>0)は型変数であり、全てのy引数はx引数によって一意に決定されることを意味する。従属関係のどちらかの辺に複数の変数が現れるなら、t -> a b
のようにスペースで区切る。上記のEの例のように、一つのクラスについて複数の従属関係をコンマで区切って並べることができる。書き得る従属関係の中には冗長なものもあるが、これらの一部は拒絶される。全く役に立たない上に、プログラム中の誤りを反映しているかもしれないからである。このような従属関係の例としてa -> a
、a -> a a
、a ->
などが挙げられる。複数の従属関係が与えられているとき、例えばa->b
, b->c
, a->c
のような場合、複数の従属関係を組み合わせることで残りものが言え、やはり冗長だが、このような場合はエラーとされない。従属関係はクラス宣言にのみ現れ、言語の中のその他の場所には現れないことに注意。特に、インスタンス宣言、クラス制約、型はどれも全く変化を被らない。
クラス宣言に従属関係を含めることで、プログラマは多引数の型クラスをより精密に指定できる。一方、コンパイラは、プログラム中のどの一点においても、そこから見えるインスタンスの集合が宣言された従属性に整合していることを保証しなければならない。例えば、以下の二つのインスタンス宣言は一つのスコープに同時に現れてはならない。Dに関する従属性に反するからである。どちらか一方であれば問題ない。
instance D Bool Int where ... instance D Bool Char where ...
また、以下のような宣言は単独でも禁止される。
instance D [a] b where ...
ここでの問題は、特定の[a]に対して、複数のbが関連づけられ、結果としてDの定義で指定された従属性に反することである。
instance D t s where ...
より一般的に、上のような宣言があったとすると、sには、tに現れる型変数しか現れてはならない。これで、tの型が既知であれば、sも一意に決定できることになる。
従属性情報を書くことの利点は、曖昧性の問題無しに、より一般的な多引数型クラスを書くことができ、より精密な型の恩恵に与ることができることである。これを解り易く示すために、集まりのクラスの例に戻り、Collects
の定義に単純な従属性注釈を付け加えよう。
class Collects e ce | ce -> e where empty :: ce insert :: e -> ce -> ce member :: e -> ce -> Bool
ここで、ce -> e
という従属関係は、要素の型eが集まりの型ceによって一意に定まることを指定している。引数の種はどちらも*であることに注意してほしい。つまりこれは構築子クラスではない。また、最初に挙げたCollectsのインスタンスは全てこの新しい定義の下で有効である。
元々の定義を使ったときに現れた曖昧性の問題はどうだろうか。empty関数の型はCollects e ce => ceのままだが、もはやこれを曖昧だとみなす必要はない。変数eは=>記号の右辺に現れていないが、Collectsクラスの従属関係から、これがceによって一意に決定されることがわかる。ceは=>記号の右側に現れているので、emptyが使われるときの文脈から得られる情報で、ceとeの両方の型を曖昧さなく決定できる。一般的に、ある型が曖昧だとされるのは、=>の左辺に、右辺の変数によって(直接または間接に)一意に決定されない変数が存在するときだけである。
利用者定義の関数により正確な型を与えることにも従属性は寄与する。これにより、より早く誤りを見つけることができるようになり、プログラマがぐちゃぐちゃな型と格闘しなくても良いようになる。前に挙げたfの定義を思い出して欲しい。
f x y = insert x y = insert x . insert y
これに対して、最初に得た型は次であった。
f :: (Collects a c, Collects b c) => a -> b -> c -> c
しかし、Collectsについての従属性情報を使うと、aとbが等しくなければならないと推論できる。両者とも、同じように第一引数がcであるCollects制約の第二引数に現れているからである。これにより、fに対して、より短く、より本性を反映した型を推論することができる。
f :: (Collects a c) => a -> a -> c -> c
同様の方法で、前に挙げたgの定義は型エラーとして処理される。
ここでは少しの例しか挙げなかったが、多引数型クラスは、従属性情報を付け加えることで、より実用的になり、曖昧さの問題が排除され、より一般的なインスタンスの集合を受け入れられるようになった、ということが明らかだろう。
インスタンス宣言は以下のような形をとる
instance (assertion
1, ...,assertion
n) =>class
type
1 ...type
m where ...
「=>
」より前の部分は文脈であり、「=>
」より後の部分はこのインスタンス宣言の頭部と呼ばれる。
GHCは、例えばC Int Bool
という制約を解決しようとするとき、全てのインスタンス宣言について、その頭部を具体化して、この制約と照合しようと試みる。以下の宣言を考えよ。
instance context1 => C Int a where ... -- (A) instance context2 => C a Bool where ... -- (B)
GHCのデフォルトの振る舞いは、解決しようとする制約に適合するインスタンスが、ただ一つ存在しなければならないというものである。例えば、制約C Int Bool
は(A)と(B)の両インスタンスに適合するので、拒絶される。C Int Char
は(A)にしか合致しないので、(A)が選ばれる。
以下に注意せよ。
照合に際して、GHCはインスタンス宣言の文脈(context1
など)を考慮しない。
(例えば(A)と(B)の両方の宣言を含むなどの)潜在的な重複があるのは問題ない。エラーになるのは、特定の制約に適合するインスタンスが複数ある場合である。
インスタンス解決の規則を緩めるフラグに関して、7.6.3.5. 重複インスタンスも見よ。
Haskell 98では、インスタンス宣言の頭部はC (T a1 ... an)
という形でなければならない。ここで、C
はクラス、T
はデータ型構築子、a1 ... an
は相異なる型変数である。多引数の型クラスについては、この規則がインスタンス頭部の各パラメタに対して適用される。(この形を持つものがただ一つで、残りが型変数であってもたぶん問題ないはずだが、今のところこれが規則である。)
GHCはこれを二通りの方向に緩和する。
-XTypeSynonymInstances
フラグが有効なら、インスタンスの頭部が型シノニムを使ってもよい。他の場合と同様、型シノニムは、その定義の右辺の型を書くための略記法に過ぎない。例えば、以下は合法である。
type Point a = (a,a) instance C (Point a) where ...
このインスタンス宣言は以下と同等である。
instance C (Int,Int) where ...
他の場合と同様に、型シノニムは完全に適用されていなければならない。例えば、以下のように書くことはできない。
instance Monad Point where ...
-XFlexibleInstances
フラグは、インスタンス宣言の頭部が任意のネストした型に言及するのを許す。例として、これが合法なインスタンス宣言になる。
instance C (Maybe Int) where ...
重複に関する規則も見よ。
-XFlexibleInstances
フラグは-XTypeSynonymInstances
も有効にする。
Haskell 98では、インスタンス宣言中の文脈における表明はC a
という形でなければならない。ここでa
は頭部に出現する型変数である。
-XFlexibleContexts
フラグは、この規則を緩め、さらに型シグネチャについての同様の規則も緩める(7.13.2. 型シグネチャの文脈を見よ)。このフラグが有効なら、インスタンス宣言の文脈は、下記の規則に従う限り、(種の正しい)任意の表明(C t1 ... tn)
から成っていてよい。
Paterson条件: 文脈中の各表明に対して、次が求められる。
その表明に、頭部よりも多く出現する型変数があってはならない
その表明中の構築子と変数の数の合計(同じ物でも複数回数える)が、頭部中のそれよりも少なくなければならない。
対応範囲条件(Coverage Condition)。クラス中のそれぞれの関数従属tvs
left ->
tvs
rightに対して、S(tvs
right)中の型変数は全てS(tvs
left)にも現れなければならない。ただし、Sは、クラス宣言中の型変数を対応するインスタンス宣言中の型に対応させる置換写像である。
これらの制約により、文脈の簡約に終わりがあることが保証される。一回簡約するごとに最悪でも構築子一つ分問題が小さくなるからである。-XUndecidableInstances
フラグ(7.6.3.4. 決定不能インスタンス)を与えれば、Paterson条件と対応範囲条件の両方が撤廃される。これらの制限がある理由についての沢山の背景資料が、論文Understanding functional dependencies via Constraint Handling Rulesに見つかる。
例を挙げると、以下のものは問題ない。
instance C Int [a] -- 多引数 instance Eq (S [a]) -- 頭部に構造のある型 -- 頭部に同じ型変数が複数回出現 instance C4 a a => C4 [a] [a] instance Stateful (ST s) (MutVar s) -- 頭部は型変数のみから成っていても良い instance C a instance (Eq a, Show b) => C2 a b -- 文脈中に型変数以外のものがあっても良い instance Show (s a) => Show (Sized s a) instance C2 Int a => C3 Bool [a] instance C2 Int a => C3 [a] b
一方で、下記は禁止される。
-- 文脈の表明が頭部より小さくない instance C a => C a where ... -- (C b b)に頭部よりも多くのbの出現がある instance C b b => Foo [b] where ...
これと同じ制約が、deriving
節で生成されたインスタンスにも適用される。従って、下記のものは許される。
data MinHeap h a = H a (h a) deriving (Show)
これは、自動導出された以下のインスタンスが上記の規則に整合するからである。
instance (Show a, Show (h a)) => Show (MinHeap h a)
上記の規則によって、ある便利なイディオムが可能になる。重複インスタンス宣言を許すとき、より特化したインスタンスが当てはまらないときに適用される「デフォルトのインスタンス」があると非常に便利である。
instance C a where op = ... -- デフォルト
7.6.3.3. インスタンス文脈に関する規則の緩和の規則でさえも厄介なことがある。例えば、次のようにして「クラスシノニム」のような効果を得たいと思うかもしれない。
class (C1 a, C2 a, C3 a) => C a where { } instance (C1 a, C2 a, C3 a) => C a where { }
次のようなシグネチャがあったとする。
f :: (C1 a, C2 a, C3 a) => ...
これは上記のものを使えば次のように書ける。
f :: C a => ...
関数従属(7.6.2. 関数従属 )についての制約は特に面倒である。通常の規則では禁止されているものの、頭部に現れない型変数を文脈中で使いたいと思うことがあるだろう。以下のような場合である。
class HasConverter a b | a -> b where convert :: a -> b data Foo a = MkFoo a instance (HasConverter a b,Show b) => Show (Foo a) where show (MkFoo value) = show (convert value)
しかし、これは危険な領域である。例えば、以下のものは、型検査器をループさせる。
class D a class F a b | a->b instance F [a] [[a]] instance (D c, F a c) => D [a] -- 「c」は頭部で言及されていない
同様に、対応範囲条件を撤廃したいと思うかもしれない。
class Mul a b c | a b -> c where (.*.) :: a -> b -> c instance Mul Int Int Int where (.*.) = (*) instance Mul Int Float Float where x .*. y = fromIntegral x * y instance Mul a b c => Mul a [b] [c] where x .*. v = map (x.*.) v
三番目のインスタンス宣言は対応範囲条件に従っていない。実際、以下の(やや奇妙な)例では、型推論がループに陥る。これは、(Mul a [b] b)
という制約を要求するからである。
f = \ b x y -> if b then x .*. [y] else y
これにもかかわらず、GHCは、より自由な規則の下で実験することを許している。-XUndecidableInstances
という実験的なフラグを使えば、Paterson条件と対応範囲条件(7.6.3.3. インスタンス文脈に関する規則の緩和に記述がある)の両方が撤廃される。停止性は、深さ固定の再帰スタックを使うことで保証される。スタックの深さを超過した場合、バックトレースのようなものが表示される。この時、-fcontext-stack=
Nで、スタックをより深くすることもできる。
一般に、7.6.3.1. インスタンスの解決で論じたように、GHCは、型クラス制約を解決するのにどのインスタンス宣言を使えば良いかが曖昧さなく決まることを要求する。この挙動を変更するフラグが二つある。-XOverlappingInstances
と-XIncoherentInstances
である。この節ではこれらを扱う。これらは両方とも動的フラグであり、(望むならLANGUAGE
プラグマを使って)モジュール単位で設定することができる。(7.20.1. LANGUAGEプラグマ)
-XOverlappingInstances
が与えられると、GHCは、7.6.3.1. インスタンスの解決に記述されているインスタンス解決規則を緩めて、最も特殊性の高いインスタンスが存在することを条件に、複数のインスタンスの適合を認める。-XIncoherentInstances
フラグは、この解決をさらに緩め、最も特殊性の高いインスタンスが存在するかどうかにかかわらず、複数のインスタンスが適合することを認める。
例として、以下を-XOverlappingInstances
を有効にしてコンパイルする場合を考えよ。
instance context1 => C Int b where ... -- (A) instance context2 => C a Bool where ... -- (B) instance context3 => C a [b] where ... -- (C) instance context4 => C Int [Int] where ... -- (D)
C Int [Int]
という制約は、(A)、(C)、(D)の各インスタンスに適合するが、最も特殊性が高いのは最後のものなので、これが選ばれる。
仮に(D)が存在しなかった場合、(A)と(C)は変わらず適合するが、どちらも、最も特殊性が高い訳ではない。この場合、-XOverlappingInstances
が有効であってもプログラムは拒絶される。-XIncoherentInstances
が有効の場合、このプログラムは受理され、(A)または(C)が恣意的に選ばれる。
あるインスタンス宣言が別のインスタンス宣言よりも特殊性が高いというのは、前者の頭部が後者の頭部の置換例であることである。例えば、(D)が(C)よりも「特殊性が高い」のは、a:=Int
の置換を施すことで(C)から(D)にたどりつけるからである。
ただし、GHCは重複インスタンスから特定のものを選ぶことに関しては保守的である。例。
f :: [b] -> [b] f x = ...
f
の右辺から、C b [b]
という制約を得たとしよう。しかし、GHCはインスタンス(C)を使うことはない。なぜなら、f
の呼び出しによっては、b
がInt
に実体化するかも知れず、その場合は(D)が最も特殊性の高いインスタンスになるからである。よって、GHCはこのプログラムを拒絶する。
ただし、(D)を含むモジュールをコンパイルする際に-XIncoherentInstances
フラグを追加すると、GHCは、後々のインスタンス化についての問題を指摘することなく、(C)を選ぶようになる。
f
に型シグネチャを与えたので、f
が指定した型を持っていることをGHCが検査せねばならなかったことに注意。そうでなく、型シグネチャを与えず、GHCに推論してもらったとしよう。この場合、GHCはC Int [b]
という制約を単純化することは避ける(前と同じ理由)が、プログラムを拒絶することはなく、以下の型を推論する。
f :: C b [b] => [b] -> [b]
これは、どのインスタンスを選ぶかの問題をf
の呼び出し側まで遅延する。その時には、型b
についてより多くが知られているだろう。 -XFlexibleContexts
フラグを使えば、この型シグネチャを自分で書くことができる。
インスタンス宣言自体についても、まったく同じ状況が発生し得る。以下のものがあるとしよう。
class Foo a where f :: a -> a instance Foo [b] where f x = ...
さらに、前と同じように、制約C Int [b]
がf
の右辺から発生するとする。制約C Int [b]
は複数のインスタンス宣言に適合するので、前と同様にGHCはこの制約を解決する方法が分からないとしてこのインスタンスを拒絶する。解決策は、インスタンス宣言の文脈にこの制約を加えて、選択を後回しにすることである。次のように。
instance C Int [b] => Foo [b] where f x = ...
(これをするには-XFlexibleInstances
が必要である)
警告: 重複インスタンスを使うときは気を付けないといけない。一貫性が失われる(つまり、プログラムの部分部分で異なるインスタンスが選ばれる)可能性があるからである。これは-XIncoherentInstances
を使っていない場合ですらそうである。以下を考えよ。
{-# LANGUAGE OverlappingInstances #-} module Help where class MyShow a where myshow :: a -> String instance MyShow a => MyShow [a] where myshow xs = concatMap myshow xs showHelp :: MyShow a => [a] -> String showHelp xs = myshow xs {-# LANGUAGE FlexibleInstances, OverlappingInstances #-} module Main where import Help data T = MkT instance MyShow T where myshow x = "Used generic instance" instance MyShow [T] where myshow xs = "Used more specific instance" main = do { print (myshow [MkT]); print (showHelp [MkT]) }
showHelp
関数では、GHCから重複のあるインスタンスが見えないので、GHCは文句を言うことなくMyShow [a]
インスタンスを使う。main
中のmyshow
の呼び出しでは、GHCは、制約MyShow [T]
を解決するに際してMain
モジュールの重複インスタンス宣言を使う。結果として、このプログラムは次のように表示する。
"Used more specific instance" "Used generic instance"
(実装されていないが、他にあり得る振る舞いとして、後のインスタンス宣言が局所的なものと重複するかもしれないという理由でHelp
モジュールを拒絶するというものがある。)
重複インスタンスや非整合(incoherent)インスタンスになるかどうかは、インスタンス宣言の性質であり、そのモジュールがコンパイルされるときに-XOverlappingInstances
と-XIncoherentInstances
が有効になっていたかどうかで決まる。(C ty1 .. tyn)
という目標制約のインスタンスを探しているとしよう。探索は次のように行なわれる。
目標制約に適合するインスタンスIを全て見つける。つまり、Iに置換を施すことで目標制約が得られるようなIを全て見つける。これらのインスタンス宣言が候補である。
目標制約と単一化し得るインスタンスで、候補でないものを全て見つける。このような非候補インスタンスは、目標制約がさらに具体化された場合に適合するようになる可能性を持っている。もしこれらが全て-XIncoherentInstances
付きでコンパイルされているなら、次に進む。そうでなければ、探索は失敗する。
以下の両方が成り立つ候補IXを全て排除する。
より特殊性の高い別の候補IYがある。つまり、IYはIXの置換例であるが、その逆は成り立たない。
IYかIXのいずれかが-XOverlappingInstances
付きでコンパイルされている。
残っている候補が一つだけなら、それを選ぶ。そうでない場合、残る候補の全てが-XInccoherentInstances
付きでコンパイルされているなら、候補を一つ恣意的に選ぶ。
これらの規則によって、ライブラリの作者は、重複インスタンスに依存したライブラリを、利用者がそれについて知らなくても良いように設計することができる。
-XIncoherentInstances
フラグを使うと、-XOverlappingInstances
フラグは自動的に有効になる。逆は真でない。
Haskellでは、インスタンス宣言に型シグネチャを書くことはできない。しかしこれができると便利なことがあるので、言語拡張-XInstanceSigs
がこれを許す。例を示す。
data T a = MkT a a instance Eq a => Eq (T a) where (==) :: T a -> T a -> Bool -- シグネチャ (==) (MkT x1 x2) (MkTy y1 y2) = x1==y1 && x2==y2
インスタンス宣言における型シグネチャは、クラス宣言内のものにインスタンスの型をあてはめたものと全く同じでなければならない。
型シグネチャを書きたいと思うスタイル上の理由の一つは、単純なドキュメントとしてである。もう一つは、スコープを持つ型変数をスコープに導入したいかもしれないからである。例。
class C a where foo :: b -> a -> (a, [b]) instance C a => C (T a) where foo :: forall b. b -> T a -> (T a, [b]) foo x (T y) = (T y, xs) where xs :: [b] xs = [x,x,x]
-XScopedTypeVariables
(7.13.8. 字句的スコープを持つ型変数 )も指定しているなら、forall b
のスコープはfoo
の定義にわたる。特にxs
の型シグネチャもこれに含まれる。
GHCは文字列リテラルの多重定義に対応している。通常、文字列リテラルは型String
を持つが、文字列リテラルの多重定義を有効にする(-XOverloadedStrings
で)と、文字列リテラルが(IsString a) => a
という型を持つようになる。
これは、通常の文字列構文を使って、例えばByteString
、Text
その他の文字列的な型を書くことができるということである。文字列リテラルは整数リテラルとほとんど同じように振る舞う。つまり、式とパターンの両方で使うことができる。リテラルがパターンで使われた場合、整数リテラルと同じ方法で、等値性のテストに置き換えられる。
クラスIsString
は次のように定義されている。
class IsString a where fromString :: String -> a
定義済みのインスタンスは一つだけで、文字列が通常通りに使えるようにする、自明なものである。
instance IsString [Char] where fromString cs = cs
IsString
クラスはデフォルトでスコープに無い。明示的に言及したい(例えば、インスタンス宣言のために)なら、GHC.Exts
モジュールからインポートすることができる。
-XOverloadedStrings
が指定されたときは、Haskellのデフォルト化機構が拡張されて、文字列リテラルにも対応するようになる。具体的には以下の通り。
デフォルト宣言におけるそれぞれの型は、Num
またはIsString
のインスタンスでなければならない。
標準のデフォルト化規則(Haskell Report, Section 4.3.4)は次のように拡張される。デフォルト化は、全ての未解決の制約が標準のクラスまたはIsString
についてであって、少くとも一つが数値クラスまたはIsString
である場合に適用される。
小さい例を示す。
module Main where import GHC.Exts( IsString(..) ) newtype MyString = MyString String deriving (Eq, Show) instance IsString MyString where fromString = MyString greet :: MyString -> MyString greet "hello" = "world" greet other = other main = do print $ greet "hello" print $ greet "fool"
パターン照合は等値比較に翻訳されるので、パターン照合のためにはEq
を自動導出することが必要だということに注意。
GHCは、リスト記法の多重定義に対応している。リストを構築するための記法を概観しよう。Haskellでは、リスト記法は以下の七通りの方法で使える。
[] -- 空リスト [x] -- x : [] [x,y,z] -- x : y : z : [] [x .. ] -- enumFrom x [x,y ..] -- enumFromThen x y [x .. y] -- enumFromTo x y [x,y .. z] -- enumFromThenTo x y z
OverloadedLists
拡張が有効だと、上記の七つの記法は以下のように脱糖される。
[] -- fromListN 0 [] [x] -- fromListN 1 (x : []) [x,y,z] -- fromListN 3 (x : y : z : []) [x .. ] -- fromList (enumFrom x) [x,y ..] -- fromList (enumFromThen x y) [x .. y] -- fromList (enumFromTo x y) [x,y .. z] -- fromList (enumFromThenTo x y z)
この拡張は、プログラマがリスト記法を使ってSet
、Map
、IntMap
、Vector
、Text
、Array
などの構造を構築することを可能にする。以下のコードでいくつか例を挙げる。
['0' .. '9'] :: Set Char [1 .. 10] :: Vector Int [("default",0), (k1,v1)] :: Map String Int ['a' .. 'z'] :: Text
リストパターンも多重定義される。OverloadedLists
が有効の場合、これらの定義は以下のように脱糖される。
f [] = ... -- f (toList -> []) = ... g [x,y,z] = ... -- g (toList -> [x,y,z]) = ...
(ここでは翻訳のためにビューパターン記法を使っている。7.3.7. ビューパターン を見よ。)
IsList
クラス上記の脱糖において、toList
、fromList
、fromListN
の各関数はIsList
クラスのメソッドであり、このクラスはGHC.Exts
モジュールからエクスポートされている。この型クラスは以下のように定義されている。
class IsList l where type Item l fromList :: [Item l] -> l toList :: l -> [Item l] fromListN :: Int -> [Item l] -> l fromListN _ = fromList
FromList
クラスとそのメソッドは、OverloadedLists
拡張と一緒に使うことを意図されている。
型関数Item
は、構造l
の要素の型を返す。
関数fromList
は、与えられたItem l
のリストから構造l
を構築する。
関数fromListN
は、入力リストの長さをヒントとして受け取る。このヒントは、fromList
よりも効率良くl
を構築するために使うことができる。与えられたヒントが入力リストの長さに等しくない場合、fromListN
の振る舞いは規定されない。
toList
はfromList
の逆であるべきである。
IsList
の新しいインスタンスを定義することは何も問題ない。これによって、全く新しいデータ型に対してもリスト記法が使えるようになる。ここにインスタンスの例をいくつか示す。
instance FromList [a] where type Item [a] = a fromList = id toList = id instance (Ord a) => FromList (Set a) where type Item (Set a) = a fromList = Set.fromList toList = Set.toList instance (Ord k) => FromList (Map k v) where type Item (Map k v) = (k,v) fromList = Map.fromList toList = Map.toList instance FromList (IntMap v) where type Item (IntMap v) = (Int,v) fromList = IntMap.fromList toList = IntMap.toList instance FromList Text where type Item Text = Char fromList = Text.pack toList = Text.unpack instance FromList (Vector a) where type Item (Vector a) = a fromList = Vector.fromList fromListN = Vector.fromListN toList = Vector.toList
-XOverloadedLists
が有効な場合、リスト記法を脱糖する際にはGHCはGHC.Exts
モジュール由来のfromList
(など)を使う。これはGHC.Exts
をインポートしなくても行なわれる。
しかし、-XRebindableSyntax
を使うと、GHCは、toList
、fromList
、fromListN
の各名前について、スコープにあるものならなんでも使うようになる。つまり、これらの関数は再束縛可能である。7.3.15. 再束縛可能な構文とPreludeの暗黙インポートと比較せよ。
現在、IsList
クラスにはデフォルト化規則が付随していない。以下のようなデフォルト宣言はもっともらしいが、このような宣言の意味を如何に規定するかについては、あまり深く考察されていない。
default ([a])
OverloadedLists
拡張は、リテラルを内容とするリストを特別扱いすることで、現在の実装からさらに改良できる。より詳しく言うと、コンパイラはこのようなリストを確保する際にコンパクトな表現を使うことが可能であり、その場合IsList
インスタンスがこのコンパクト表現を活用できるようにすることができる。この能力があれば、OverloadedStrings
拡張をOverloadedLists
拡張に統合することが現実味を帯びるであろう(現在のところ、文字列リテラルは特別扱いされ、静的に確保されたコンパクトな表現を使うことができる)。