Michael は彼のブログ記事で既に適切な説明を提供していますが、(不自然ではあるが比較的小さい) 例を使って説明しようと思います。
次の拡張機能が必要です。
{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
CanFilter
パラメータが 1 つだけのよりも単純なクラスを定義しましょう。2 つのインスタンスの動作の違いを示したいので、クラスの 2 つのコピーを定義しています。
class Twice1 f where
twice1 :: f -> f
class Twice2 f where
twice2 :: f -> f
それでは、各クラスのインスタンスを定義しましょう。の場合Twice1
、型変数が直接同じになるように修正し、 の場合、Twice2
それらが異なることを許可しますが、等式制約を追加します。
instance Twice1 (a -> a) where
twice1 f = f . f
instance (a ~ b) => Twice2 (a -> b) where
twice2 f = f . f
違いを示すために、次のような別のオーバーロードされた関数を定義しましょう。
class Example a where
transform :: Int -> a
instance Example Int where
transform n = n + 1
instance Example Char where
transform _ = 'x'
今、私たちは違いを見ることができるポイントにいます. 定義したら
apply1 x = twice1 transform x
apply2 x = twice2 transform x
GHCに推論された型を尋ねると、それが得られます
apply1 :: (Example a, Twice1 (Int -> a)) => Int -> a
apply2 :: Int -> Int
何故ですか?Twice1
関数のソースとターゲットの型が同じ場合にのみ、インスタンスが起動します。および与えられたコンテキストについてtransform
は、私たちはそれを知りません。GHC は右側が一致した場合にのみインスタンスを適用するため、未解決のコンテキストが残ります。と言おうとするとapply1 0
、オーバーロードを解決するのに十分な情報がまだないことを示す型エラーが発生します。Int
この場合、通過するために結果の型を明示的に指定する必要があります。
ただし、Twice2
では、インスタンスは任意の関数型用です。GHC はすぐにそれを解決し (GHC はバックトラックしないため、インスタンスが明らかに一致する場合は常に選択されます)、前提条件を確立しようとします: この場合、等式制約により、結果の型が強制さInt
れ、Example
制約も解決します。apply2 0
それ以上の型注釈なしで言うことができます。
したがって、これは GHC のインスタンス解決に関するかなり微妙な点であり、ここでの等値制約は、GHC の型チェッカーが、ユーザーによる型注釈を少なくする必要がある方法で役立ちます。