ConstrainedMonoidを探しています
最近、非常によく似た問題が発生しました。この問題は、この投稿の最後に記載されている方法で最終的に解決しました(モノイドではなく、型の述語を使用)。ConstrainedMonoid
しかし、私は挑戦してクラスを書こうとしました。
アイデアは次のとおりです。
class ConstrainedMonoid m where
type Compatible m (t1 :: k) (t2 :: k) :: Constraint
type TAppend m (t1 :: k) (t2 :: k) :: k
type TZero m :: k
memptyC :: m (TZero m)
mappendC :: (Compatible m t t') => m t -> m t' -> m (TAppend m t t')
ささいなインスタンスがありますが、実際には何も新しいものは追加されていません(R
s型引数を交換しました):
data K = T0 | T1 | T2 | T3 | T4
data R a (t :: K) = R String (Maybe (String -> a))
instance ConstrainedMonoid (R a) where
type Compatible (R a) T1 T1 = ()
type Compatible (R a) T2 T2 = ()
type Compatible (R a) T3 T3 = ()
type Compatible (R a) T4 T4 = ()
type Compatible (R a) T0 y = ()
type Compatible (R a) x T0 = ()
type TAppend (R a) T0 y = y
type TAppend (R a) x T0 = x
type TAppend (R a) T1 T1 = T1
type TAppend (R a) T2 T2 = T2
type TAppend (R a) T3 T3 = T3
type TAppend (R a) T4 T4 = T4
type TZero (R a) = T0
memptyC = R "" Nothing
R s f `mappendC` R t g = R (s ++ t) (g `mplus` f)
残念ながら、これには多くの冗長な型インスタンスが必要です(OverlappingInstances
型族では機能しないようです)が、型レベルでも値レベルでもモノイドの法則を満たしていると思います。
ただし、閉鎖されていません。これは、でインデックス付けされた一連の異なるモノイドのようなものK
です。それがあなたが望むものなら、それで十分なはずです。
もっと欲しいなら
別のバリアントを見てみましょう。
data B (t :: K) = B String deriving Show
instance ConstrainedMonoid B where
type Compatible B T1 T1 = ()
type Compatible B T2 T2 = ()
type Compatible B T3 T3 = ()
type Compatible B T4 T4 = ()
type TAppend B x y = T1
type TZero B = T3
memptyC = B ""
(B x) `mappendC` (B y) = B (x ++ y)
これはあなたのドメインで理にかなっているケースかもしれません-しかし、それはもはやモノイドではありません。そして、その1つを作成しようとすると、上記のインスタンスと同じになりますが、が異なりTZero
ます。私は実際にはここで推測しているだけですが、私の直感では、有効なモノイドインスタンスはR a
;のようなものだけであることがわかります。異なる単位値でのみ。
さもなければ、あなたは必ずしも連想的ではない何か(そしておそらくターミナルオブジェクトと)になってしまい、それは構成の下で閉じられません。また、構成をsに制限したい場合K
は、単位値が失われます。
より良い方法(IMHO)
これが私が実際に私の問題を解決した方法です(モノイドはとにかく意味をなさなかったので、当時はモノイドについてさえ考えていませんでした)。Compatible
このソリューションは基本的に、2つのタイプの述語として残されている「制約プロデューサー」を除くすべてを取り除きます。
type family Matching (t1 :: K) (t2 :: K) :: Constraint
type instance Matching T1 T1 = ()
type instance Matching T2 T1 = ()
type instance Matching T1 T2 = ()
type instance Matching T4 T4 = ()
のように使用
foo :: (Matching a b) => B a -> B b -> B Int
これにより、互換性の定義、および必要な構成の種類(必ずしもモノイドではない)を完全に制御でき、より一般的です。無限の種類に拡張することもできます。
-- pseudo code, but you get what I mean
type instance NatMatching m n = (IsEven m, m > n)
最後の2つのポイントに答える:
はい、あなたはあなたの種類で十分に多くのタイプを定義する必要があります。しかし、とにかく彼らは自明であるべきだと思います。それらをグループに分割したり、再帰型を定義したりすることもできます。
主に、制約の定義と、おそらくファクトリメソッド()の2つの場所でインデックスタイプの意味を思い出す必要がありますmkB1 :: String -> B T1
。しかし、タイプに適切な名前が付けられていれば、それは問題ではないと思います。(ただし、非常に冗長になる可能性があります。これを回避する方法はまだ見つかりません。おそらくTHが機能するでしょう。)
これはもっと簡単でしょうか?
私が実際に書きたいのは次のとおりです。
type family Matching (t1 :: K) (t2 :: K) :: Constraint
type instance Matching T1 y = ()
type instance Matching x T1 = ()
type instance Matching x y = (x ~ y)
これには許可されない重大な理由があるのではないかと思います。しかし、たぶん、それは実装されていないだけです...
編集:今日、私たちはまさにこれを行う閉じた型族を持っています。