这是this question的后续问题,其中允许部分应用(~)
类型运算符的答案。
考虑以下(愚蠢)类型系列:
type family SillyT a b :: Constraint
data D (c :: * -> Constraint) where
D :: Proxy c -> D c
然后这样的事情无效:
D (Proxy :: (Proxy (SillyT Int)))
但是,如果我将SillyT
包装在类似的类中:
class SillyT a b => SillyC a b
instance SillyT a b => SillyC a b
然后我可以这样做:
D (Proxy :: (Proxy (SillyC Int)))
一切正常。然而,在类中包装似乎有点愚蠢和重复,但这是部分应用类型系列的唯一方法吗?
答案 0 :(得分:1)
您提到的这两个定义:
从{Haskell的类型检查器的角度来看,type family SillyT a b :: Constraint
和class ... => SillyC a b
非常非常不同。
如果您看到前者,您唯一知道的是在应用两个类型变量后得到Constraint
,但是您没有告诉GHC在仅应用一个后会发生什么。您应该能够将SillyT
定义为type family SillyT a :: * -> Constraint
,然后更多地告诉GHC。
考虑一下构造函数变量。如果你告诉GHC某事是Functor
,GHC知道它的“形状”是f a
,其中a
是某些f
的最后一个参数。您必须告诉GHC它可以以相同的方式分析SillyT
的结果,因此GHC可以肯定,它始终可以访问查看结果类型的最后一个参数。
类型类在这里更具表现力。它们是一种类型,而不是类型转换(如类型族),GHC可以对它们的变量进行“模式匹配”,因此您不会遇到上述限制。此外,类型类允许您定义重叠实例,这在使用类型族时是不可能的。但是,函数依赖性不如类型族灵活,因为除非GHC支持不可预测的上下文,否则使用类型deps很难摆脱不必要的自由类型变量。
我希望它澄清一些事情:)