对DataKinds扩展名感到困惑

时间:2018-12-17 09:52:48

标签: haskell

我从Basic Type Level Programming in Haskell学习了Haskell的类型编程,但是当它引入DataKinds扩展名时,该示例似乎有些令人困惑:

{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat

现在,Nat被提升为Kind,没关系。但是ZeroSucc呢?

我尝试从GHCi获取一些信息,所以我输入:

:kind Zero

它给出了

Zero :: Nat

没关系,Zero是类型为Nat的类型,对吗?我尝试:

:type Zero

它仍然给出:

Zero :: Nat

这意味着Zero具有类型Nat,这是不可能的,因为Nat是一种类型,不是吗? Nat既是类型又是类型??

另一个令人困惑的事情是,上面的博客还提到,当创建Nat类型时,有两种新类型:'Zero'Succ是自动创建的。当我再次从GHCi尝试它时:

:kind 'Zero

给予

'Zero :: Nat

:type 'Zero

给予

 Syntax error on 'Zero

好的,它证明'Zero是一种类型。但是创建'Zero和'Succ'的目的是什么?

2 个答案:

答案 0 :(得分:9)

在未扩展的Haskell中,声明

data A = B

定义两个新实体,每个实体在计算和类型级别:

  1. 在类型级别,一个名为A(类型为*)的新基本类型,和
  2. 在计算级别上,一个名为B(类型为A)的新基础计算。

打开DataKinds时,声明

data A = B

现在定义四个新实体,一个在计算级别,两个在类型级别,一个在种类级别:

  1. 在种类级别上,新的基本种类A
  2. 在类型级别,新的基本类型'B(类型A
  3. 在类型级别,新的基本类型A(类型*)和
  4. 在计算级别,一个新的基础计算B(类型为A)。

这是我们以前拥有的严格的超集:旧的(1)现在是(3),旧的(2)现在是(4)。

这些新实体说明了您描述的以下互动:

:type Zero
Zero :: Nat
:kind 'Zero
'Zero :: Nat
:type 'Zero
Syntax error on 'Zero

我认为很清楚如何解释前两个。它解释的最后一个是因为'Zero是类型级别的东西-您不能要求类型的类型,而只能要求计算的类型!

现在,在Haskell中,每个出现名称的地方,从周围的语法中都可以清楚地看出该名称是要用作计算级名称,类型级名称还是种类级名称。因此,在类型级别必须在'B中加入对号有点烦人-毕竟,编译器知道处于类型级别,因此可以并不是指未提升的计算级别B。因此,为方便起见,允许您在明确的刻度线上留下勾号。

从现在开始,我将区分“后端”和“表面语法”,在“后端”中只有上述四个实体,它们始终是唯一的;“表面语法”是您在文件中键入的内容并传递到包含歧义但更方便的GHC。使用这种术语,在表面语法中,可以写出以下内容,其含义如下:

Surface syntax    Level           Back end
Name              computation     Name
Name              type            Name if that exists; 'Name otherwise
'Name             type            'Name
Name              kind            Name
---- all other combinations ----  error

这说明了您进行的首次互动(以及上面唯一无法解释的互动):

:kind Zero
Zero :: Nat

因为必须将:kind应用于类型级别的事物,所以编译器知道表面语法名称Zero必须是类型级别的事物。由于没有类型级别的后端名称Zero,因此它尝试使用'Zero并获得成功。

怎么可能是模棱两可的?好了,请注意,上面我们用一个声明在类型级别定义了两个个新实体。为了简化介绍,我将等式左侧和右侧的新实体命名为不同的东西。但是让我们看看如果我们稍微调整一下声明会发生什么:

data A = A

我们仍将引入四个新的后端实体:

  1. 亲切A
  2. 类型'A(类型A
  3. 类型A(类型*)和
  4. 计算A(类型为A)。

哇!现在在类型级别上既有'A也有A。如果您在表面语法中不打勾,它将使用(3),而不是(2)-您可以使用表面语法'A显式选择(2)。

此外,这不必全部通过单个声明来完成。一个声明可能会产生已标记的版本,而另一个则可能会产生未标记的版本;例如

data A = B
data C = A

将从第一个声明中引入类型级别的后端名称A,从第二个声明中引入类型级别的后端名称'A

答案 1 :(得分:4)

{-# LANGUAGE DataKinds #-}不会改变data关键字通常所做的事情:它仍然创建类型Nat以及两个值构造函数ZeroSucc。该扩展的作用是,它使您可以像使用类型一样使用类型,并像使用类型一样使用值。

因此,如果在类型级表达式中使用Nat,它将仅用作无聊的Haskell98类型。只有在毫无疑问地 kind 级别的表达式中使用它时,您才能获得同类版本。

这种自动提升有时可能会导致名称冲突,我认为这是'语法的原因:

{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat
data Zero

现在,Zero本身就是普通的(空)数据类型Zero :: *,所以

*Main> :k Zero
Zero :: *

原则上,由于DataKindsZero现在也是从值构造函数Zero :: Nat提升的类型,但是data Zero却掩盖了它。因此,引用语法总是引用提升类型,而不是直接定义类型:

*Main> :k 'Zero
'Zero :: Nat