37

ClassyPreludeの発表を読んでいて、ここにたどり着きました。

instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a where
    filter = filterFunc

筆者はその後、これはうまくいかないと述べました。

instance (CanFilterFunc b a) => CanFilter (c -> c) a where
    filter = filterFunc

c左側の制約とはまったく関係がないので、これは私には理にかなっています。

ただし、記事で言及されておらず、理解できないのは、これが機能しない理由です。

instance (CanFilterFunc b a) => CanFilter (b -> b) a where
    filter = filterFunc

これが最初に述べた定義と異なる理由を誰かが説明できますか? おそらく、GHC 型推論の実際の例が役立つでしょうか?

4

2 に答える 2

56

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 の型チェッカーが、ユーザーによる型注釈を少なくする必要がある方法で役立ちます。

于 2012-07-19T06:02:24.147 に答える