5

重複しないタイプのセットが2つあり、これら2つの和集合である他のセットを作成したいと思います。コードサンプル:

class A a
class B b
class AB ab

instance A a => AB a
instance B b => AB b

GHC 6.12.3では、これをエラーメッセージで宣言することはできません。

    重複するインスタンス宣言:
      インスタンス(A a)=> AB a-playground.hs:8:9-19で定義
      インスタンス(B b)=> AB b-playground.hs:9:9-19で定義

私は、この宣言が重複するインスタンスの制御を失うことにつながることを理解しています。これはAB a、インスタンスが後で発生する可能性があるためです(A aそしてB b、それを処理する簡単な方法がわかりません)。
同じ動作をするためには、いくつかの「回避策」が必要だと思います。

次のようなPSバリアント:

newtype A a => WrapA a = WrapA a
newtype B b => WrapB b = WrapB b

instance A a => AB (WrapA a)
instance B b => AB (WrapB b)

data WrapAB a b = A a => WrapA a
                | B b => WrapB b

instance AB (WrapAB a b)

そして、このタイプのいくつかをラップする他のものは私のニーズに合いません(サードパーティが宣言したタイプのクラスによる実装を選択します)

@camccannへのコメント: フラグのマージ/選択タイプを制御するためにフラグを追加するのは素晴らしいアイデアですが、重複するインスタンスの競合などは避けたいと思います。この回答に興味のある人のために、圧縮されたバリアント:

data Yes
data No

class IsA a flag | a -> flag
class IsB b flag | b -> flag

instance Delay No flag => IsA a flag
instance Delay No flag  => IsB b flag

instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab

class AB' isA isB ab
instance (A a) => AB' Yes No a
instance (B b) => AB' No Yes b
instance (A a) => AB' Yes Yes a

class Delay a b | a -> b
instance Delay a a

instance IsA Bool Yes
instance A Bool
4

1 に答える 1

3

私の知る限り、これを達成するための「良い」方法はありません。あなたはどこかにがらくたを追加することで立ち往生しています。ラッパー型は必要ないので、私が考えることができるもう1つのオプションは、代わりにクラス定義をいじることです。つまり、type-metaprogramming-landに移動します。

さて、このアプローチが「いい」ではない理由は、クラスの制約が基本的に取り消せないからです。GHCが制約を確認すると、それを維持し、制約を満たすことができない場合、コンパイルは失敗します。これは、クラスインスタンスの「交差」には問題ありませんが、「結合」には役立ちません。

これを回避するには、直接のクラス制約ではなく、型レベルのブール値を持つ型述語が必要です。これを行うために、関数従属性を持つマルチパラメーター型クラスを使用して型関数を作成し、統合が遅れた重複インスタンスを使用して「デフォルトインスタンス」を記述します。

まず、楽しい言語プラグマが必要です。

{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}

いくつかの型レベルのブール値を定義します。

data Yes = Yes deriving Show
data No = No deriving Show

class TypeBool b where bval :: b
instance TypeBool Yes where bval = Yes
instance TypeBool No where bval = No

このTypeBoolクラスは厳密には必要ありません。私は主に、での作業を避けるためにこのクラスを使用しundefinedます。

次に、結合したい型クラスのメンバーシップ述語を記述します。デフォルトのインスタンスは、フォールスルーケースとして機能します。

class (TypeBool flag) => IsA a flag | a -> flag
class (TypeBool flag) => IsB b flag | b -> flag 

instance (TypeBool flag, TypeCast flag No) => IsA a flag
instance (TypeBool flag, TypeCast flag No) => IsB b flag

もちろん、TypeCast制約はOlegの悪名高い型統合クラスです。そのためのコードは、この回答の最後にあります。ここでは、結果タイプの選択を遅らせる必要があります。fundepは、最初のパラメーターが2番目のパラメーターを決定し、デフォルトのインスタンスは完全に汎用であると述べているためNo、インスタンスヘッドに直接配置すると、述語は常にfalseと評価されます。役に立たなかった。代わりにを使用TypeCastすると、GHCが最も具体的なオーバーラップしたインスタンスを選択するまで待機します。これにより、No特定のインスタンスが見つからなくなった場合にのみ結果が強制されます。

型クラス自体に、厳密には必要ではない別の調整を行います。

class (IsA a Yes) => A a where
    fA :: a -> Bool
    gA :: a -> Int

class (IsB b Yes) => B b where
    fB :: b -> Bool
    gB :: b -> b -> String

クラスコンテキスト制約により、一致する述語インスタンスも記述せずにクラスのインスタンスを記述した場合、後で非常に混乱するバグではなく、すぐに不可解なエラーが発生します。また、デモンストレーションの目的で、クラスにいくつかの関数を追加しました。

次に、ユニオンクラスは2つの部分に分割されます。最初のインスタンスには、メンバーシップ述語を適用して2番目の述語を呼び出す単一のユニバーサルインスタンスがあり、述語の結果を実際のインスタンスにマップします。

class AB ab where 
    fAB :: ab -> Bool
instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab where
    fAB = fAB' (bval :: isA) (bval :: isB)

class AB' isA isB ab where fAB' :: isA -> isB -> ab -> Bool
instance (A a) => AB' Yes No a where fAB' Yes No = fA
instance (B b) => AB' No Yes b where fAB' No Yes = fB
instance (A ab) => AB' Yes Yes ab where fAB' Yes Yes = fA
-- instance (B ab) => AB' Yes Yes ab where fAB' Yes Yes = fB

両方の述語がtrueの場合、Aインスタンスを明示的に選択していることに注意してください。コメントアウトされたインスタンスは同じことを行いますが、B代わりに使用します。両方を削除することもできます。その場合、2つのクラスの排他的論理和が得られます。ここbvalで私はTypeBoolクラスを使用しています。正しい型ブール値を取得するための型シグネチャにも注意してください。これにはScopedTypeVariables、上記で有効にしたが必要です。

まとめとして、いくつかのインスタンスを試してみてください。

instance IsA Int Yes
instance A Int where
    fA = (> 0)
    gA = (+ 1)

instance IsB String Yes
instance B String where
    fB = not . null
    gB = (++)

instance IsA Bool Yes
instance A Bool where
    fA = id
    gA = fromEnum

instance IsB Bool Yes
instance B Bool where
    fB = not
    gB x y = show (x && y)

GHCiで試してみる:

> fAB True
True
> fAB ""
False
> fAB (5 :: Int)
True
> fAB ()
No instance for (AB' No No ())
  . . .

そして、これがOlegTypeCastの好意によるコードです。

class TypeCast   a b   | a -> b, b->a   where typeCast   :: a -> b
class TypeCast'  t a b | t a -> b, t b -> a where typeCast'  :: t->a->b
class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b
instance TypeCast'  () a b => TypeCast a b where typeCast x = typeCast' () x
instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast''
instance TypeCast'' () a a where typeCast'' _ x  = x
于 2010-07-17T17:00:17.307 に答える