マイケルはすでにブログ記事でよく説明していますが、私はそれを(考案された、しかし比較的小さい)例で説明しようとします。
私たちは、次の拡張子が必要です:
{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
ちょうど一つのパラメータで、CanFilter
よりも簡単であるクラスを定義してみましょう。
class Twice1 f where
twice1 :: f -> f
class Twice2 f where
twice2 :: f -> f
それでは、各クラスのインスタンスを定義してみましょう:私は2つのインスタンス間の動作の違いを証明したいので、私は、クラスの2つのコピーを定義しています。 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のタイプチェッカーが、ユーザーによるより少ないタイプの注釈を必要とするように役立ちます。
ありがとうございました。私はそれのようなものを疑っていましたが、あなたの単純化された例は事をもっとはっきりとさせました。 – Clinton