5

オーバーラップ動作を持つインスタンスを定義するために、高度なオーバーラップトリックに似たようなことをしようとしています。フィールドが存在する場合はフィールドのインスタンスを使用するタプルのインスタンスを派生させようとしていますfst。そうでない場合は、フィールドが存在する場合はそのインスタンスを使用しますsnd。これにより、最終的に、重複するインスタンスに関する誤ったエラーが表示されます。

まず、キッチンのシンクは 以外は全部使っていOverlappingInstancesます。

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

また、ポリカインドProxyとタイプ レベル orを使用してい:||:ます。

import Data.Proxy

type family (:||:) (a :: Bool) (b :: Bool) :: Bool
type instance (:||:) False a = a
type instance (:||:) True a = True

Aで遊ぶのは非常に単純なクラスです。インスタンスThingAがあります。しません。AThingB

class A x where
    traceA :: x -> String

data ThingA = ThingA
data ThingB = ThingB

instance A ThingA where
    traceA = const "ThingA"

次の部分の目標は、またはインスタンスがある限り定義されるAインスタンスを作成することです。インスタンスがあれば、 を返します。インスタンスがなくてもインスタンスがある場合は、を返します。(x, y)A xA yA x("fst " ++) . traceA . fstA xB x("snd " ++) . traceA . fst

A最初のステップは、インスタンス ヘッドと照合することにより、インスタンスがあるかどうかをテストする機能依存関係を作成することです。これは、高度なオーバーラップの記事からの通常のアプローチです。

class APred (flag :: Bool) x | x -> flag

instance APred 'True ThingA
instance (flag ~ 'False) => APred flag x

両方にインスタンスがあるかどうかを判断できればx、インスタンスがあるかどうかを判断できます。yA(x, y)

instance (APred xflag x, APred yflag y, t ~ (xflag :||: yflag)) => APred t (x, y)

ここで、高度なオーバーラップの単純な例から離れて、インスタンスA xまたはA yインスタンスを使用するかどうかを選択するための 2 つ目の機能依存関係を紹介します。( との混同を避けるために、 BoolforChoosesおよび とは異なる種類を使用できます。)SwitchAAPred

class Chooses (flag :: Bool) x | x -> flag

インスタンスがあるA x場合は常に を選択'Trueし、そうでない場合は'False.

instance (APred 'True x) => Chooses 'True (x, y) 
instance (flag ~ 'False) => Chooses flag (x, y)

次に、高度なオーバーラップの例と同様に、選択用の追加の型変数とすべてのメンバーの引数をA除いてと同じクラスを定義します。Proxy

class SwitchA (flag :: Bool) x where
    switchA :: Proxy flag -> x -> String

これはインスタンスを定義するのが簡単です

instance (A x) => SwitchA 'True (x, y) where
    switchA _ = ("fst " ++) . traceA . fst

instance (A y) => SwitchA 'False (x, y) where
    switchA _ = ("snd " ++) . traceA . snd

SwitchA最後に、と同じ型のインスタンスがあれば、インスタンス(x, y) Choosesを定義できA (x, y)ます。

instance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy flag)

ここまでのすべてが見事にコンパイルされます。ただし、追加しようとすると

traceA (ThingA, ThingB)

次のエラーが表示されます。

    Overlapping instances for Chooses 'True (ThingA, ThingB)
      arising from a use of `traceA'
    Matching instances:
      instance APred 'True x => Chooses 'True (x, y)
        -- Defined at defaultOverlap.hs:46:10
      instance flag ~ 'False => Chooses flag (x, y)
        -- Defined at defaultOverlap.hs:47:10
    In the first argument of `print', namely
      `(traceA (ThingA, ThingA))'

何が起きてる?のインスタンスを探すときにこれらのインスタンスが重複するのはなぜですかChooses 'True ...。であることがすでにわかっているinstance flag ~ 'False => Chooses flag ...場合、インスタンスが一致しないはずはありませんか?flag'True

逆に試してみると

traceA (ThingB, ThingA)

エラーが発生します

    No instance for (A ThingB) arising from a use of `traceA'
    In the first argument of `print', namely
      `(traceA (ThingB, ThingA))'

コンパイラに、意図されていないことを実行させようとしたときに何が起こっているのかについての洞察は役に立ちます。

編集 - 単純化

この回答からの観察に基づいて、Chooses完全に取り除き、書くことができます

instance (APred choice x, SwitchA choice (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy choice)

これにより、次の問題が解決されます。traceA (ThingB, ThingA)

4

1 に答える 1

2

実際に何が起こっているかを確認するには、Choosesクラスを見てください。具体的には、次の場合は遅延していないことに注意してくださいFalse(つまり、値を true にする必要があるとすぐに判断できない場合)。

chooses :: Chooses b x =>  x -> Proxy b 
chooses _ = Proxy

>:t chooses (ThingA, ())
chooses (ThingA, ()) :: Proxy 'True
>:t chooses (ThingB, ())

<interactive>:1:1: Warning:
    Couldn't match type 'True with 'False
    In the expression: chooses (ThingB, ())

怠け者ではない理由それほど単純ではありません。最も具体的なインスタンスは、

instance (APred 'True x) => Chooses 'True (x, y)

が最初に試されます。これを確認するために、コンパイラはAPred. ここでは、instance APred 'True ThingAがあるため一致しませんThingB。そのため、2 番目のインスタンスにフォールスルーし、flag(Chooses で) False と統合します。その場合、制約APred 'True xは保持できません! したがって、型チェックは失敗します。あなたが得た型エラーは奇妙なものですが、OverlappingInstances が有効になっていないためだと思います。あなたのコードでそれをオンにすると、次のようになります。

>traceA (ThingA, ThingA)
"fst ThingA"
>traceA (ThingB, ThingA)

<interactive>:43:1:
    Couldn't match type 'True with 'False
    In the expression: traceA (ThingB, ThingA)
    In an equation for `it': it = traceA (ThingB, ThingA)

これは予想どおりです。True と False の型は統合できません。

修正は非常に簡単です。クラスを型関数に変換します。型関数は本質的に同等ですが、「より怠惰」です。これは非常に手の込んだものです。申し訳ありませんが、なぜそれが機能するのかについてのより良い説明がありません。

type family APred' x :: Bool where 
  APred' ThingA = True
  APred' x = False 

type family Chooses' x :: Bool where 
  Chooses' (x, y) = APred' x 

instance (Chooses' (x,y) ~ flag, SwitchA flag (x, y)) => A (x, y) where
    traceA = switchA (Proxy :: Proxy flag)

あなたは今、「ああ、型族を使うためにすべてのコードを書き直さなければならない」と考えています。これは当てはまりません。なぜなら、いつでも型ファミリーを関数依存関係を持つ Class 述語に「下げる」ことができるからです。

instance Chooses' x ~ b => Chooses b x 

これで、元のインスタンスinstance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where ...が期待どおりに機能します。

>traceA (ThingA, ThingA)
"fst ThingA"
>traceA (ThingB, ThingA)
"snd ThingA"
于 2014-12-13T01:21:19.397 に答える