类型家庭返回(a - >类型)

时间:2018-03-02 16:18:34

标签: haskell type-families type-level-computation

我有一个班级:

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

2 个答案:

答案 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 ~ ga ~ 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,我们就很好。

不饱和类型构造函数

但是,允许部分应用

类型构造函数,例如MaybeEither。那是因为它们具有某些属性(即生成性和注入性),这使我们能够推导出上述等式。

鉴于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

型的lambda

那么如果解除这个限制会怎么样?正如@pigworker所提到的,这将允许我们拥有类型级lambda(好吧,不一定是lambdas供我们使用,但它会带来与我们有lambdas一样的问题):

目前,如果我们知道某些ghExtractType gExtractType 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应该可以正常工作。