オーバーラップ動作を持つインスタンスを定義するために、高度なオーバーラップトリックに似たようなことをしようとしています。フィールドが存在する場合はフィールドのインスタンスを使用するタプルのインスタンスを派生させようとしています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
があります。しません。A
ThingB
class A x where
traceA :: x -> String
data ThingA = ThingA
data ThingB = ThingB
instance A ThingA where
traceA = const "ThingA"
次の部分の目標は、またはインスタンスがある限り定義されるA
インスタンスを作成することです。インスタンスがあれば、 を返します。インスタンスがなくてもインスタンスがある場合は、を返します。(x, y)
A x
A y
A x
("fst " ++) . traceA . fst
A x
B x
("snd " ++) . traceA . fst
A
最初のステップは、インスタンス ヘッドと照合することにより、インスタンスがあるかどうかをテストする機能依存関係を作成することです。これは、高度なオーバーラップの記事からの通常のアプローチです。
class APred (flag :: Bool) x | x -> flag
instance APred 'True ThingA
instance (flag ~ 'False) => APred flag x
両方にインスタンスがあるかどうかを判断できればx
、インスタンスがあるかどうかを判断できます。y
A
(x, y)
instance (APred xflag x, APred yflag y, t ~ (xflag :||: yflag)) => APred t (x, y)
ここで、高度なオーバーラップの単純な例から離れて、インスタンスA x
またはA y
インスタンスを使用するかどうかを選択するための 2 つ目の機能依存関係を紹介します。( との混同を避けるために、 Bool
forChooses
および とは異なる種類を使用できます。)SwitchA
APred
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)