この節及び次の節では、型クラスに関する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.12.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 to0 genum
が補充され、(Generic a, GEnum (Rep a)) => [a]
という型で型検査される。
我々は、GHCにおける総称プログラミングを単純にするためにデフォルトシグネチャを使っている(7.22. 総称プログラミング)。
関数従属は、“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.12.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 ...
「=>
」より前の部分は文脈であり、「=>
」より後の部分はこのインスタンス宣言の頭部と呼ばれる。
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.12.2. 型シグネチャの文脈を見よ)。このフラグが有効なら、インスタンス宣言の文脈は、下記の規則に従う限り、(種の正しい)任意の表明(C t1 ... tn)
から成っていてよい。
Paterson条件: 文脈中の各表明に対して、次が求められる。
その表明に、頭部よりも多く出現する型変数があってはならない
その表明中の構築子と変数の数の合計(同じ物でも複数回数える)が、頭部中のそれよりも少なくなければならない。
対応範囲条件(Coverage Condition)。クラス中のそれぞれの関数従属tvs
left ->
tvs
rightに対して、S(tvs
right)中の型変数は全てS(tvs
left)にも現れなければならない。ただし、Sは、クラス宣言中の型変数を対応するインスタンス宣言中の型に対応させる置換写像である。
これらの制約により、文脈の簡約に終わりがあることが保証される。一回簡約するごとに最悪でも構築子一つ分問題が小さくなるからである。-XUndecidableInstances
フラグ(7.6.3.3. 決定不能インスタンス)を与えれば、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.2. インスタンス文脈に関する規則の緩和の規則でさえも厄介なことがある。例えば、次のようにして「クラスシノニム」のような効果を得たいと思うかもしれない。
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.2. インスタンス文脈に関する規則の緩和に記述がある)の両方が撤廃される。停止性は、深さ固定の再帰スタックを使うことで保証される。スタックの深さを超過した場合、バックトレースのようなものが表示される。この時、-fcontext-stack=
Nで、スタックをより深くすることもできる。
一般に、GHCでは、型クラス制約を解決するのにどのインスタンス宣言を使えば良いかが曖昧さなく決まることが要求される。この挙動を変更するフラグが二つある。-XOverlappingInstances
と-XIncoherentInstances
である。この節ではこれらを扱う。これらは両方とも動的フラグであり、(望むならOPTIONS_GHC
プラグマを使って)モジュール単位で設定することができる。(4.2.2. ソースファイル中のコマンド行オプション)
例えば、C Int Bool
という制約を解決しようとするときは、全てのインスタンス宣言について、その頭部を具体化して、この制約と照合しようと試みられる。例として、以下のようなインスタンス宣言があったとしよう。
instance context1 => C Int a where ... -- (A) instance context2 => C a Bool where ... -- (B) instance context3 => C Int [a] where ... -- (C) instance context4 => C Int [Int] where ... -- (D)
インスタンス(A)および(B)は制約C Int Bool
に適合するが、(C)や(D)は適合しない。照合の際、インスタンス宣言の文脈(context1
など)は無視される。GHCのデフォルトの振る舞いは、解決中の制約に適合するインスタンスがただ一つ存在しなければならないというものである。潜在的な重複(例えば(A)と(B)の両方がスコープにある場合)があっても問題はない。特定の制約に複数のインスタンスが適合して初めてエラーが報告される。
-XOverlappingInstances
フラグは、最も特殊性の高いインスタンスが存在することを条件に、複数のインスタンスの適合を認めるものである。例えば、C Int [Int]
には(A)、(C)、(D)の各インスタンスが適合するが、最後のものの特殊性が最も高いので、これが選ばれる。最も特殊性の高いものがない場合は、プログラムが拒絶される。
ただし、GHCは重複インスタンスから特定のものを選ぶことに関しては保守的である。例。
f :: [b] -> [b] f x = ...
f
の右辺から、C Int [b]
という制約を得たとしよう。しかし、GHCはインスタンス(C)を使うことはない。なぜなら、f
の呼び出しによっては、b
がInt
に実体化するかも知れず、その場合は(D)が最も特殊性の高いインスタンスになるからである。よって、GHCはこのプログラムを拒絶する。(-XIncoherentInstances
フラグを追加すると、GHCは、後々のインスタンス化についての問題を指摘することなく、(C)を選ぶようになる)。
f
に型シグネチャを与えたので、f
が指定した型を持っていることをGHCが検査せねばならなかったことに注意。そうでなく、型シグネチャを与えず、GHCに推論してもらったとしよう。この場合、GHCはC Int [b]
という制約を単純化することは避ける(前と同じ理由)が、プログラムを拒絶することはなく、以下の型を推論する。
f :: C Int [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
が有効になっていたかどうかで決まる。より精密には、インスタンス発見の過程では、以下が成り立つ。
問題となっている制約にIA、IBという二つのインスタンス宣言が適合し、かつ、
IBはIAの置換例である(がその逆ではない)。つまり、IBがIAよりも厳密に特殊性が高い。
IAかIBの少なくともいずれかが-XOverlappingInstances
付きでコンパイルされている。
なら、特殊性が低いインスタンスであるIAは無視される。
問題となっている制約に対して、あるインスタンス宣言が、適合はしないものの単一化する場合を考えてみよう。この場合、制約がさらに具体化すると、そのインスタンス宣言が適合する可能性がある。通常、GHCは、これを理由として、他の制約を採用しない。しかし、もしそのインスタンス宣言が-XIncoherentInstances
付きでコンパイルされているなら、GHCはこの「単一化するか?」の検査を行わない。
これらの規則によって、ライブラリの作者は、重複インスタンスに依存したライブラリを、利用者がそれについて知らなくても良いように設計することができる。
-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.12.7. 字句的スコープを持つ型変数
)も指定しているなら、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
を自動導出することが必要だということに注意。