Haskell 哈斯凯尔·辛格尔顿:SNat给我们带来了什么
我在试着抚摸哈斯克尔的单身汉 在报纸上 在他的博客中 Richard Eisenberg定义了数据类型Nat,该数据类型使用peano公理定义自然数:Haskell 哈斯凯尔·辛格尔顿:SNat给我们带来了什么,haskell,dependent-type,type-level-computation,singleton-type,Haskell,Dependent Type,Type Level Computation,Singleton Type,我在试着抚摸哈斯克尔的单身汉 在报纸上 在他的博客中 Richard Eisenberg定义了数据类型Nat,该数据类型使用peano公理定义自然数: data Nat = Zero | Succ Nat 通过使用语言扩展DataTypes,此数据类型被提升到类型级别。 数据构造函数Zero和Succ被提升为类型构造函数Zero和Succ。 这样我们就得到了每一个自然数在类型级别上唯一的对应类型。例如,对于3,我们得到了“成功”(“成功”(“成功”零)。 所以我们现在有了自然数作为类型 然后,
data Nat = Zero | Succ Nat
通过使用语言扩展DataTypes,此数据类型被提升到类型级别。
数据构造函数Zero和Succ被提升为类型构造函数Zero和Succ。
这样我们就得到了每一个自然数在类型级别上唯一的对应类型。例如,对于3,我们得到了“成功”(“成功”(“成功”零)。
所以我们现在有了自然数作为类型
然后,他在值级别上定义function plus,在类型级别上定义type family plus
使添加操作可用。
使用Singleton库的promote函数/QuasiNoter,我们可以自动
从Plus函数创建Plus类型族。因此,我们可以避免自己编写类型族
到目前为止还不错
使用GADT语法,他还定义了一个数据类型SNat:
基本上,他只将Nat类型包装到SNat构造函数中。
为什么这是必要的?我们得到了什么?
Nat和SNat的数据类型是否不同构?为什么SNat是单身汉,而Nat不是
单身汉?在这两种情况下,每种类型都有一个单独的值,即相应的自然数。我们得到了什么?嗯。单身汉的现状是尴尬的,但目前是必要的解决办法,我们越早摆脱他们越好 让我看看能不能把情况弄清楚。我们有一个数据类型
Nat
:
data Nat = Zero | Suc Nat
(战争的起因甚至比Suc中c的数量还要琐碎。)
类型Nat
具有在类型级别无法区分的运行时值。Haskell类型系统当前具有replacement属性,这意味着在任何类型良好的程序中,您可以用具有相同范围和类型的替代子表达式替换任何类型良好的子表达式,并且程序将继续保持类型良好。例如,您可以重写
if <b> then <t> else <e>
我们可能希望计算给定元素副本的向量(可能是应用程序
实例的一部分)。这可能是一个好主意,给类型
vec :: forall (n :: Nat) (x :: *). x -> Vec n x
但这可能有效吗?为了制作某物的n
副本,我们需要在运行时了解n
:程序必须决定是部署VNil
并停止,还是部署vcon
并继续运行,并且需要一些数据来完成这项工作。一个很好的线索是forall
量词,它是参数化的:它表示量化信息仅对类型可用,并被运行时删除
Haskell目前在依赖量化(所有的功能)和运行时擦除之间强制执行完全虚假的一致性。它不支持依赖但未擦除的量词,我们通常称之为pi
。vec
的类型和实现应该类似
vec :: pi (n :: Nat) -> forall (x :: *). Vec n x
vec 'Zero x = VNil
vec ('Suc n) x = VCons x (vec n x)
其中,pi
-位置的参数是用类型语言编写的,但数据在运行时可用
那么我们该怎么办呢?我们使用单例来间接捕获类型级数据的运行时副本的含义
现在,SZero
和SSuc
生成运行时数据SNat
与Nat
不同构:前者具有类型Nat->*
,而后者具有类型*
,因此尝试使它们同构是一种类型错误。Nat
中有很多运行时值,类型系统没有区分它们;在每个不同的SNat n
中,只有一个运行时值(值得一提),因此类型系统无法区分它们的事实是无关紧要的。关键是每个SNat n
对于每个不同的n
都是不同的类型,GADT模式匹配(模式可以是已知匹配的GADT类型的更具体实例)可以完善我们对n
的了解
我们现在可以写信了
vec :: forall (n :: Nat). SNat n -> forall (x :: *). x -> Vec n x
vec SZero x = VNil
vec (SSuc n) x = VCons x (vec n x)
通过利用允许细化类型信息的运行时分析的唯一形式,单例允许我们弥合运行时和类型级数据之间的差距。怀疑它们是否真的有必要是很明智的,而且它们现在是,只是因为这一差距尚未消除。@pigworker:非常感谢!你写道:“尽管‘零’和‘Suc’存在于类型级别,但将它们称为‘类型’是没有帮助的,目前这样做的人应该停止”你如何称呼‘零和’Suc?我认为好名字总是有用的。什么类型的类?不,类型应用程序与另一个无关的区别有关:显式与隐式。这与类型级别和运行时完全不同。谷歌“米尔纳的巧合”。@Jogger我会称之为“零和”Suc类型的水平值构造函数。“类型级表达式”可能是一个口头禅,但这是将它们与“表达式”区分开来的错误决定所付出的代价。“Typex”可以成为一个可接受的替代品。“它们没有类型
*
,因此无法对值进行分类,而这正是名副其实的类型所能做的。”这里是否存在自相矛盾的地方,或者*
在某种意义上对值进行了分类,我不知道?类型*
对值进行了分类,因此,这些东西值得称为“类型”。问一下是明智的,因为我把*
称为一种“类型”,它对哪些值进行了分类。答案是“类型”,它是类型级别值的一种。类型当前不可用作运行时值的事实(a)对将*
视为类型的有效性没有影响,(b)是一个设计错误,应予以纠正,允许删除数据。可键入的
。
vec :: forall (n :: Nat) (x :: *). x -> Vec n x
vec :: pi (n :: Nat) -> forall (x :: *). Vec n x
vec 'Zero x = VNil
vec ('Suc n) x = VCons x (vec n x)
data SNat :: Nat -> * where
SZero :: SNat Zero
SSuc :: SNat n -> SNat (Suc n)
vec :: forall (n :: Nat). SNat n -> forall (x :: *). x -> Vec n x
vec SZero x = VNil
vec (SSuc n) x = VCons x (vec n x)