我从Basic Type Level Programming in Haskell学习了Haskell的类型编程,但是当它引入DataKinds
扩展名时,该示例似乎有些令人困惑:
{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat
现在,Nat
被提升为Kind
,没关系。但是Zero
和Succ
呢?
我尝试从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'
的目的是什么?
答案 0 :(得分:9)
在未扩展的Haskell中,声明
data A = B
定义两个新实体,每个实体在计算和类型级别:
A
(类型为*
)的新基本类型,和B
(类型为A
)的新基础计算。打开DataKinds
时,声明
data A = B
现在定义四个新实体,一个在计算级别,两个在类型级别,一个在种类级别:
A
'B
(类型A
)A
(类型*
)和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
我们仍将引入四个新的后端实体:
A
,'A
(类型A
)A
(类型*
)和A
(类型为A
)。哇!现在在类型级别上既有'A
也有A
。如果您在表面语法中不打勾,它将使用(3),而不是(2)-您可以使用表面语法'A
显式选择(2)。
此外,这不必全部通过单个声明来完成。一个声明可能会产生已标记的版本,而另一个则可能会产生未标记的版本;例如
data A = B
data C = A
将从第一个声明中引入类型级别的后端名称A
,从第二个声明中引入类型级别的后端名称'A
。
答案 1 :(得分:4)
{-# LANGUAGE DataKinds #-}
不会改变data
关键字通常所做的事情:它仍然创建类型Nat
以及两个值构造函数Zero
和Succ
。该扩展的作用是,它使您可以像使用类型一样使用类型,并像使用类型一样使用值。
因此,如果在类型级表达式中使用Nat
,它将仅用作无聊的Haskell98类型。只有在毫无疑问地 kind 级别的表达式中使用它时,您才能获得同类版本。
这种自动提升有时可能会导致名称冲突,我认为这是'
语法的原因:
{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat
data Zero
现在,Zero
本身就是普通的(空)数据类型Zero :: *
,所以
*Main> :k Zero
Zero :: *
原则上,由于DataKinds
,Zero
现在也是从值构造函数Zero :: Nat
提升的类型,但是data Zero
却掩盖了它。因此,引用语法总是引用提升类型,而不是直接定义类型:
*Main> :k 'Zero
'Zero :: Nat