我有一个班级:
class C (g :: [a] -> Type) where
type ExtractType g :: a -> Type
但我不确定如何编写类型族实例
instance C X where
type ExtractType X = ???
来自Type
的{p> *
为GHC.Types
。
例如,假设我有一个名为NHList (ts::[(Symbol,Type)])
,然后ExtractType NHList
应该返回(Symbol,Type) -> Type
并等同于Snd
。
答案 0 :(得分:3)
对比两个签名:
type ExtractType g :: a -> Type
type ExtractType g (x :: a) :: Type
它可能看起来只是一种语法差异,但它还有更多的东西。
两个定义都产生相同类型的签名:
ghci> :kind ExtractType
ExtractType :: ([a] -> *) -> a -> *
那有什么区别?
第一个定义声明ExtractType
是一个类型系列(类型函数),它接受一个参数,并返回类似a -> Type
的东西。
第二个说ExtractType
是两个参数的类型族,返回类似Type
的东西。使用我们在术语级别上的直觉,这两者可能听起来是等价的,但等价依赖于部分应用:能够在不完全使其参数完全饱和的情况下传递函数。但是,类型族不允许这样做:它们必须始终完全饱和。
更具体地说,您的示例看起来像
type family Snd (t :: (a, b)) :: b where
Snd '(_, b) = b
...
instance C NHList where
type ExtractType NHList = Snd
^^^
这里,Snd
是不饱和的,因为它需要一个参数,但它没有。
解决方案很简单:饱和Snd
。为此,我们采用第二个版本:
class C (g :: [a] -> Type) where
type ExtractType g (x :: a) :: Type
instance C NHList where
type ExtractType NHList a = Snd a
^^^^^
现在,我说类型系列必须始终完全饱和 - 但为什么会这样?有几个原因,这是其中之一。
GHC类型系统中使用的约束求解器做出以下假设:
鉴于已知的相等f a ~ g b
,我们可以推导出f ~ g
和a ~ b
。
fancyId :: f a ~ g b => a -> b
fancyId = id -- `a` must be the same as `b`
当f a
是类型构造函数时,类型可以采用f
形式,如Maybe
,如Maybe Int
。如果f
可能是一个类型系列怎么办?
type family Dumb a b where
Dumb _ b = b
通过部分申请,我们可以拥有类似Dumb Int ~ Dumb String
的内容,GHC很容易派生Int ~ String
。由于类型族必须完全饱和的限制,我们甚至不能写下这种平等,让我们远离问题。相反,Dumb Int String ~ Dumb String a
现在涉及两个完全饱和的家庭,所以他们可以先减少到String ~ a
,我们就很好。
类型构造函数,例如Maybe
或Either
。那是因为它们具有某些属性(即生成性和注入性),这使我们能够推导出上述等式。
鉴于Either Int ~ Either a
,我们可以了解Int ~ a
,因为Either
是单射的。 Snd
不是唯一的:Snd '(Int, String) ~ Snd '(Char, String)
成立,但这并不意味着'(Int, String) ~ '(Char, String)
。
总结以上,签名
type ExtractType g :: a -> Type
实际上意味着ExtractType
只接受一个参数,并返回类型构造函数。
data Proxy (a :: k) = Proxy
...
type ExtractType g = Proxy
那么如果解除这个限制会怎么样?正如@pigworker所提到的,这将允许我们拥有类型级lambda(好吧,不一定是lambdas供我们使用,但它会带来与我们有lambdas一样的问题):
目前,如果我们知道某些g
和h
,ExtractType g
和ExtractType h
相同,则必须表示它们返回相同类型的构造函数,例如如上Proxy
(这是使用第一个定义)。这里的等式理论非常简单,因为我们可以很容易地确定两个类型构造函数何时在定义上是相等的。当ExtractType g
被允许返回任何旧类型函数时,事情会变得更复杂:两个类型函数何时相等?
我们可以对lambda术语使用定义相等,即将它们缩减为正常形式,然后使用alpha等价,但缺少强规范化属性,这个概念相等是不可判定的。这不一定是个问题,但这不是GHC所做的(还有)。
答案 1 :(得分:0)
以下是一个例子:
instance C X where
type instance ExtractType X = Proxy
鉴于您的更新,我希望您需要将课程更改为type ExtractType g (x :: a) :: Type
(并启用ScopedTypeVariables
)。然后type ExtractType X (sym, ty) = ty
应该可以正常工作。