Joe*_*hoi 5 haskell types data-kinds
我从Haskell中的基本类型级编程学习Haskell的类型编程,但是当它引入DataKinds扩展时,这个例子似乎有些令人困惑:
{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat
Run Code Online (Sandbox Code Playgroud)
现在,Nat升职Kind,没关系.但如何Zero和Succ?
我尝试从GHCi获取一些信息,所以我键入:
:kind Zero
Run Code Online (Sandbox Code Playgroud)
它给
Zero :: Nat
Run Code Online (Sandbox Code Playgroud)
没关系,Zero是一种类型Nat,对吗?我尝试:
:type Zero
Run Code Online (Sandbox Code Playgroud)
它仍然给出:
Zero :: Nat
Run Code Online (Sandbox Code Playgroud)
这意味着Zero有类型Nat,这是不可能的,因为Nat是一种不是类型,对吗?难道Nat是这两个类型和种类?
而其他令人困惑的事情是,上面的博客也提到,在创建Nat种类时,有两种新类型:'Zero并且'Succ是自动创建的.当我再次从GHCi尝试时:
:kind 'Zero
Run Code Online (Sandbox Code Playgroud)
给
'Zero :: Nat
Run Code Online (Sandbox Code Playgroud)
和
:type 'Zero
Run Code Online (Sandbox Code Playgroud)
给
Syntax error on 'Zero
Run Code Online (Sandbox Code Playgroud)
好的,它证明这'Zero是一种类型.但是创造'Zero和' Succ'?? 的目的是什么?
Dan*_*ner 10
在未扩展的Haskell中,声明
data A = B
Run Code Online (Sandbox Code Playgroud)
定义了两个新实体,分别在计算和类型级别:
A(类型*)的新基本类型,和B(类型A)的新基础计算.当你打开时DataKinds,声明
data A = B
Run Code Online (Sandbox Code Playgroud)
现在定义了四个新实体,一个在计算级别,两个在类型级别,一个在类型级别:
A,'B(类型A),A(类型*),和B(类型A).这是我们之前所拥有的严格超集:旧(1)现在是(3),而旧(2)现在是(4).
这些新实体解释了您描述的以下交互:
:type Zero
Zero :: Nat
:kind 'Zero
'Zero :: Nat
:type 'Zero
Syntax error on 'Zero
Run Code Online (Sandbox Code Playgroud)
我认为很清楚它如何解释前两个.它解释的最后一个因为'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
Run Code Online (Sandbox Code Playgroud)
这解释了您的第一次互动(以及上面唯一未解释的互动):
:kind Zero
Zero :: Nat
Run Code Online (Sandbox Code Playgroud)
因为:kind必须应用于类型级别的东西,编译器知道表面语法名称Zero必须是类型级别的东西.由于没有类型级别的后端名称Zero,因此它会尝试'Zero,并获得命中.
怎么会模糊不清?好吧,请注意上面我们在类型级别定义了两个带有一个声明的新实体.为了简化介绍,我将方程式左侧和右侧的新实体命名为不同的东西.但是,让我们看看如果我们稍微调整声明会发生什么:
data A = A
Run Code Online (Sandbox Code Playgroud)
我们仍然介绍四个新的后端实体:
A,'A(种类A),A(种类*),和A(类型A).哎呦!现在有一个'A和一个A类型级别.如果在表面语法中省略勾号,它将使用(3)而不是(2) - 并且您可以使用表面语法明确选择(2)'A.
更重要的是,这不一定只发生在一个声明中.一个声明可能产生勾选的版本而另一个声明可能产生未勾选的版本; 例如
data A = B
data C = A
Run Code Online (Sandbox Code Playgroud)
A将从第一个声明引入一个类型级别的后端名称,并'A从第二个声明中引入一个类型级别的后端名称.
{-# LANGUAGE DataKinds #-}不会改变data关键字通常的作用:它仍然创建一个类型Nat和两个值构造函数Zeroand Succ。这个扩展的作用是,它允许你像使用种类一样使用这样的类型,像使用类型一样使用值。
因此,如果您Nat在类型级表达式中使用,它只会将其用作无聊的 Haskell98 类型。只有在明确的kind级别表达式中使用它时,才能获得 kind 版本。
这种自动提升有时会导致名称冲突,我认为这就是'语法的原因:
{-# LANGUAGE DataKinds #-}
data Nat = Zero | Succ Nat
data Zero
Run Code Online (Sandbox Code Playgroud)
现在,Zero它本身就是普通(空)数据类型Zero :: *,所以
*Main> :k Zero
Zero :: *
Run Code Online (Sandbox Code Playgroud)
原则上,由于DataKinds,Zero现在也是从值构造函数中提升的类型Zero :: Nat,但这被data Zero. 因此,引用语法总是指提升类型,而不是直接定义的类型:
*Main> :k 'Zero
'Zero :: Nat
Run Code Online (Sandbox Code Playgroud)