我的数据类型是Monoid
的一个实例,所以我可以得到很好的值组合:
data R a = R String (Maybe (String → a))
instance Monoid (R a) where
mempty = R "" Nothing
R s f `mappend` R t g = R (s ++ t) (case g of Just _ → g; Nothing → f)
接下来,我不希望将所有R a
值相互组合,在我的域中没有意义。所以我介绍幻像类型t
:
{-# LANGUAGE DataKinds, KindSignatures #-}
data K = T1 | T2
data R (t ∷ K) a = R String (Maybe (String → a))
instance Monoid (R t a) where …
所以我有“限制”值:
v1 ∷ R T1 Int
v2 ∷ R T2 Int
-- v1 <> v2 => type error
和“unrestricted”:
v ∷ R t Int
-- v <> v1 => ok
-- v <> v2 => ok
但现在我遇到了问题。谈到v30
,例如:
data K = T1 | … | T30
)。我可以通过使用类型级自然来获得幻影类型的无限来源(治愈比疾病更糟,不是吗?)是否有更简单的方法来限制作曲?
答案 0 :(得分:3)
寻找ConstrainedMonoid
我最近遇到了一个非常类似的问题,我最终解决了本文末尾描述的方式(不涉及monoid,但使用类型的谓词)。但是,我接受了挑战并试图写一个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
类型的参数):
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)
这可能是一个在您的域中有意义的情况 - 但是,它不再是一个幺半群。如果您尝试制作其中一个,它将与上面的实例相同,只是使用不同的TZero
。我实际上只是在这里猜测,但我的直觉告诉我,唯一有效的monoid实例正好类似于R a
;只有不同的单位价值。
否则,你最终会得到一些不是必然关联的东西(我认为可能还有一个终端对象),这是不在合成下关闭。如果你想限制组合等于K
s,你将失去单位价值。
更好的方式(恕我直言)
以下是我实际解决问题的方法(当时我甚至没有想到幺半群,因为它们无论如何都没有意义)。除了Compatible
“约束生成器”之外,该解决方案基本上剥离了所有内容,它作为两种类型的谓词保留:
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)
回答你的最后两点:
是的,你必须定义足够多的类型。但我认为无论如何他们应该自我解释。您也可以将它们分成组,或者定义递归类型。
您必须在两个地方提醒索引类型的含义:约束的定义,以及工厂方法(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)
我担心这有一个不被允许的严重理由;然而,也许,它只是没有实现......
编辑:如今,我们有closed type families,这正是这样做的。