私の知る限り、これを達成するための「良い」方法はありません。あなたはどこかにがらくたを追加することで立ち往生しています。ラッパー型は必要ないので、私が考えることができるもう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