クラスおよびインスタンス宣言

クラス宣言

この節及び次の節では、型クラスに関するGHCの拡張を説明する。論文Type classes: exploring the design space (Simon Peyton Jones, Mark Jones, Erik Meijer)には、多くの背景事項が書かれている。

これらの拡張は全て-fglasgow-extsフラグで有効になる。

多引数の型クラス

-XMultiParamTypeClassesフラグが有効なら、以下のような、多引数の型クラスが許される。

  class Collection c a where
    union :: c a -> c a -> c a
    ...etc.

クラス宣言のスーパークラス

Haskell 98では、クラス宣言の文脈(スーパークラスを導入するのはこれである)は単純でなければならない。すなわち、すべての述語が、型変数にクラスを適用した形でなければならない。-XFlexibleContextsフラグ(7.8.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 { ... }

この場合、CDのスーパークラスであるが、Cのクラス演算であるopDに言及するのは問題ない。(DCのスーパークラスにするのは駄目である)

クラスメソッドの型

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フラグ)。

関数従属

関数従属は、“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.8.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 -> aa -> a aa -> などが挙げられる。複数の従属関係が与えられているとき、例えば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 ( assertion1, ..., assertionn) => class type1 ... typem where ...

=>」より前の部分は文脈であり、「=>」より後の部分はこのインスタンス宣言の頭部と呼ばれる。

インスタンス頭部に関する規則の緩和

Haskell 98では、インスタンス宣言の頭部はC (T a1 ... an)という形でなければならない。ここで、Cはクラス、Tはデータ型構築子、a1 ... anは相異なる型変数である。GHCはこれを二通りの方向に緩和する。

  • -XFlexibleInstancesフラグは、インスタンス宣言の頭部が任意のネストした型に言及するのを許す。例として、これが合法なインスタンス宣言になる。

      instance C (Maybe Int) where ...
    

    重複に関する規則も見よ。

  • -XTypeSynonymInstancesフラグが有効なら、インスタンスの頭部が型シノニムを使ってもよい。他の場合と同様、型シノニムは、その定義の右辺の型を書くための略記法に過ぎない。例えば、以下は合法である。

      type Point = (Int,Int)
      instance C Point   where ...
      instance C [Point] where ...
    

    一方、以下のものを付け加えると、コンパイラは、重複(実は全く同じ)インスタンスだとして文句をいう。

      instance C (Int,Int) where ...
    

    他の場合と同様に、型シノニムは完全に適用されていなければならない。例えば、以下のように書くことはできない。

      type P a = [[a]]
      instance Monad P where ...
    

インスタンス文脈に関する規則の緩和

Haskell 98では、インスタンス宣言中の文脈における表明はC aという形でなければならない。ここでaは頭部に出現する型変数である。

-XFlexibleContextsフラグは、この規則を緩め、さらに型シグネチャについての同様の規則も緩める(7.8.2. 型シグネチャの文脈を見よ)。このフラグが有効なら、インスタンス宣言の文脈は、下記の規則に従う限り、(類の正しい)任意の表明(C t1 ... tn)から成っていてよい。

  1. Paterson条件: 文脈中の各表明に対して、次が求められる。

    1. その表明に、頭部よりも多く出現する型変数があってはならない

    2. その表明中の構築子と変数の数の合計(同じ物でも複数回数える)が、頭部中のそれよりも少なくなければならない。

  2. 対応範囲条件(Coverage Condition)。クラス中のそれぞれの関数従属tvsleft -> tvsrightに対して、S(tvsright)中の型変数は全てS(tvsleft)にも現れなければならない。ただし、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の呼び出しによっては、bIntに実体化するかも知れず、その場合は(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が有効になっていたかどうかで決まる。インスタンス宣言をインポートし、利用するモジュールでは、フラグはどちらも必要ない。特に、インスタンス発見の過程では、以下が成り立つ。

  • この過程で、あるインスタンス宣言が無視されるのは、(a)より特殊性の高いものが見付かった、かつ(b)そのインスタンス宣言が-XOverlappingInstances付きでコンパイルされていた場合である。特殊性の高い方のインスタンスがコンパイルされたときのフラグは関係ない。

  • 問題となっている制約に対して、あるインスタンス宣言が、適合はしないものの単一化する場合を考えてみよう。この場合、制約がさらに具体化すると、そのインスタンス宣言が適合する可能性がある。通常、GHCは、これを理由として、他の制約を採用しない。しかし、もしそのインスタンス宣言が-XIncoherentInstances付きでコンパイルされているなら、GHCはこの「単一化するか?」の検査を行わない。

これらの規則によって、ライブラリの作者は、重複インスタンスに依存したライブラリを、利用者がそれについて知らなくても良いように設計することができる。

あるインスタンスが-XOverlappingInstancesなしでコンパイルされると、より特殊性の高いインスタンスでそのインスタンスを部分的に覆い隠すことは不可能である。場合によってはこれは不便かもしれない。もしかしたら、覆い隠される方のインスタンスではなく覆い隠す方のインスタンスがフラグ付きでコンパイルされなければならない、という規則にした方が良いのかもしれない。あるいは、利用地点で-XOverlappingInstancesフラグが使われていれば、元のインスタンス宣言がどのようにコンパイルされたかにかかわらず、利用地点においてそれを覆い隠すインスタンスを宣言し、使うことを認めるべきかもしれない。(ただし、利用地点の正確な位置を特定するのは難しいことがあるのに注意)。我々はこれらの点に関するフィードバックを待っている。

-XIncoherentInstancesフラグを使うと、-XOverlappingInstancesフラグは自動的に有効になる。逆は真でない。

文字列リテラルの多重定義

GHCは文字列リテラルの多重定義に対応している。通常、文字列リテラルは型Stringを持つが、文字列リテラルの多重定義を有効にする(-XOverloadedStringsで)と、文字列リテラルが(IsString a) => aという型を持つようになる。

これは、通常の文字列構文を使って、パックされた文字列やその他の文字列的な型を書くことができるということである。文字列リテラルは整数リテラルとほとんど同じように振る舞う。つまり、式とパターンの両方で使うことができる。リテラルがパターンで使われた場合、整数リテラルと同じ方法で、等値性のテストに置き換えられる。

クラス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を自動導出することが必要だということに注意。