[英]What exactly is the kind “*” in Haskell?
在Haskell中,(值级)表达式被分类为类型 ,可以用::
like来表示: 3 :: Int
, "Hello" :: String
, (+ 1) :: Num a => a -> a
。 类似地,类型被分类为种类 。 在GHCi中,您可以使用以下命令检查类型表达式的类型:kind
或:k
:
> :k Int
Int :: *
> :k Maybe
Maybe :: * -> *
> :k Either
Either :: * -> * -> *
> :k Num
Num :: * -> Constraint
> :k Monad
Monad :: (* -> *) -> Constraint
有一些定义, *
是“具体类型”或“值”或“运行时值”。 例如,参见Learn You A Haskell 。 那是真的吗? 我们有几个 问题, 关于种 ,在经过处理的话题,但它会是不错的规范和准确的解释*
。
究竟是什么*
是什么意思? 它与其他更复杂的类型有什么关系?
此外, DataKinds
或PolyKinds
扩展是否会改变答案?
首先, *
不是通配符! 它通常也被称为“明星”。
流血边注 :截至2015年2月,提出了简化GHC的子系统(7.12或更高版本)的建议 。 该页面对GHC 7.8 / 7.10故事进行了很好的讨论。 展望未来,GHC可能会放弃类型和种类之间的区别,使用* :: *
。 参见Weirich,Hsu和Eisenberg,System FC with Explicit Kind Equality 。
Haskell 98报告在此上下文中定义*
:
符号
*
表示所有nullary类型构造函数的种类。
在这种情况下,“nullary”只是意味着构造函数不带参数。 Either
是二元的; 它可以应用于两个参数: Either ab
。 Maybe
是一元的; 它可以应用于一个参数: Maybe a
。 Int
是无效的; 它可以应用于没有参数。
这个定义本身有点不完整。 包含完全应用的一元,二元等类型构造函数的表达式也具有kind *
,例如Maybe Int :: *
。
如果我们围绕GHC文档,我们会得到更接近“可以包含运行时值”的定义。 GHC评论页面“Kinds”表示“' *
'是盒装值。像Int
和Maybe Float
这样的东西有点*
。” 另一方面, 版本7.4.1的GHC用户指南声明*
是“提升类型”的类型。 (当修改PolyKinds
部分时,该段落未被保留。)
盒装价值和提升类型有点不同。 根据GHC评论页面“TypeType” ,
如果类型的表示不是指针,则取消装箱。 未装箱的类型也未被取消。
如果它具有底部作为元素,则提升类型。 闭包始终具有提升类型:即,Core中的任何let-bound标识符必须具有提升类型。 在操作上,抬起的物体是可以进入的物体。 只有提升类型可以与类型变量统一。
因此, ByteArray#
(内存的原始块类型)被装箱,因为它被表示为指针,但由于底部不是元素而未被提升 。
> undefined :: ByteArray#
Error: Kind incompatibility when matching types:
a0 :: *
ByteArray# :: #
因此,旧的用户指南定义似乎比GHC评论更准确: *
是提升类型的类型。 (反过来说, #
是那种未提升的类型。)
请注意,如果类型*
总是被提升,对于任何类型的t :: *
您可以使用undefined :: t
或其他一些机制构建一个“值”来创建底部。 因此,即使像Void
这样的“逻辑上无人居住”的类型也可以有一个值,即底部。
所以看来,是的, *
表示可以包含运行时值的类型,如果undefined
是您对运行时值的想法。 (这不是一个完全疯狂的想法,我不认为。)
有几种扩展使这种类型的系统更加生动。 其中一些是平凡的: KindSignatures
允许我们编写类型注释 ,如类型注释。
ConstraintKinds
添加了类型Constraint
,大致是=>
左侧的那种。
DataKinds
允许我们引入除*
和#
之外的新类型,就像我们可以引入具有data
, newtype
和type
新类型一样。
使用DataKinds
每个data
声明(可能适用的条款和条件)都会生成一个提升的类型声明。 所以
data Bool = True | False
介绍了常用的值构造函数和类型名称; 此外,它产生了一种新的 , Bool
和两种类型: True :: Bool
和False :: Bool
。
PolyKinds
引入了类变量 。 这只是一种说“对任何类型k
”的方式,就像我们在类型级别上说“for any type t
”一样。 至于我们的朋友*
以及它是否仍然意味着“具有值的类型”,我想你可以说一个类型t :: k
其中k
是一个种类变量可以包含值,如果k ~ *
或k ~ #
。
在种类语言的最基本形式中,只有种类*
和种类构造函数->
,那么*
就是那种可以与价值观建立关系的东西; 没有什么不同的类型可以是一种价值观。
存在用于对值进行分类的类型。 为了进行类型检查,具有相同类型的所有值都是可互换的,因此类型检查器只需要关注类型,而不是特定值。 因此,我们拥有所有实际价值所在的“价值水平”,以及其类型所在的“类型水平”。 “type-of”关系形成两个级别之间的链接,单个类型是(通常)多个值的类型。 Haskell使这两个层次非常明确; 这就是为什么你可以有data Foo = Foo Int Chat Bool
声明,你已经声明了一个类型级别的东西Foo
(一个带有kind *
的类型)和一个值级别的东西Foo
(一个类型为Int -> Char -> Bool -> Foo
的构造函数Int -> Char -> Bool -> Foo
)。 涉及的两个Foo
只是简单地引用不同层次上的不同实体,Haskell将它们完全分开,以至于它总能告诉你所指的是什么级别,因此可以允许(有时令人困惑的)不同级别的事物具有相同的名称。
但是一旦我们引入了自己具有结构的类型(比如Maybe Int
,它是一个类型构造函数, Maybe
应用于Int
类型),那么我们在类型级别存在的东西实际上并不存在于一种类型的关系中任何价值观。 没有类型只是Maybe
值,只有类型Maybe Int
(和Maybe Bool
, Maybe ()
,甚至Maybe Void
等)的值。 因此,我们需要对类型级别的事物进行分类,原因与我们需要对值进行分类的原因相同; 只有某些类型表达式实际上代表了可以作为值类型的东西,但是它们中的许多可以互换地用于“种类检查”(对于它被声明为类型的值级别事物而言它是否是正确的类型是一个不同级别的问题)。 1
所以*
(通常被称为“类型”)是基本类型; 它是所有类型级别的东西,可以说是值的类型。 Int
; 因此它的类型是*
。 Maybe
没有值,但它需要一个参数并生成一个具有值的类型; 这让我们有点像___ -> *
。 我们可以通过观察Maybe
的参数被用作Just a
出现的值的类型来填补空白,因此它的参数也必须是一种值(带有种类*
),所以Maybe
必须有种类* -> *
。 等等。
当你处理只涉及星和箭的种类时,只有种类*
的类型表达式是值的类型。 任何其他类型(例如* -> (* -> * -> *) -> (* -> *)
)仅包含其他“类型级实体”,这些实体不是包含值的实际类型。
PolyKinds
我理解, PolyKinds
并没有真正改变这种情况。 它只允许您在类级别进行多态声明,这意味着它会将变量添加到我们的类型语言中(除了星号和箭头)。 所以现在我可以考虑类型级别的事物k -> *
; 这可以实例化为* -> *
或(* -> *) -> *
或(* -> (* -> *)) -> *
。 我们获得了与(a -> b) -> [a] -> [b]
类型级别获得的完全相同的权力; 我们可以用一个包含变量的类型编写一个map
函数,而不必分别编写每个可能的map函数。 但是仍然只有一种类型包含类型值的类型: *
。
DataKinds
还为这种语言引入了新的东西。 实际上它的作用是让我们声明包含新类型级实体的任意新类型(正如普通data
声明允许我们声明任意新类型,其中包含新的值级实体)。 但它并没有让我们通过所有3个层面的实体对应来声明事物; 如果我有data Nat :: Z | S Nat
data Nat :: Z | S Nat
并使用DataKinds
将其提升到类型级别,然后我们在类型级别上存在两个名为Nat
不同的东西(作为值级别Z
, SZ
, S (SZ)
等类型),并且在种类级别(作为类型级别 Z
, SZ
, S (SZ)
的种类)。 类型级 Z
不是任何值的类型; 值 Z
驻留在类型级别 Nat
(其又是类型*
),而不是类型级别Z
因此, DataKinds
将新的用户定义的东西添加到种类语言中,这种类型语言可以是类型级别的新用户定义的东西,但仍然存在这样的情况:可以作为值类型的唯一类型级别的东西是种类的*
。
我所知道的那种真正改变了这种语言的唯一补充就是@ChristianConkle的回答中提到的种类,比如#
(我相信现在还有更多的东西?我不是非常了解“低级“类型,如ByteArray#
)。 这些类型具有GHC需要知道的以不同方式处理的值(例如,不假设它们可以被盒装和延迟评估),即使涉及多态函数,因此我们不能仅仅附加他们需要的知识对这些值的类型进行不同的处理,或者在调用它们的多态函数时会丢失它们。
1因此,“类型”一词可能有点令人困惑。 有时,它用于指代实际上与价值层面上的事物建立关系的事物(这是当人们说“ Maybe
不是类型,它是类型构造函数”时使用的解释)。 有时它用来指代存在于类型级别的任何东西(在这种解释下, Maybe
实际上是一种类型)。 在这篇文章中,我试图非常明确地引用“类型级别的东西”,而不是使用“类型”作为简写。
对于试图了解类型的初学者(您可以将它们视为类型的类型),我推荐了“ 了解您的Haskell”一书的这一章。
我个人认为这种方式:
您有具体的类型,例如Int
, Bool
, String
, [Int]
, Maybe Int
或Either Int String
。
所有这些都有*
。 为什么? 因为他们不能再将任何类型作为参数; Int
,是Int
; 一个Maybe Int
是一个Maybe Int
。 什么Maybe
或[]
或Either
有关系吗?
当你说Maybe
,你没有具体的类型,因为你没有指定它的参数。 Maybe Int
或Maybe String
是不同的但两者都有*
种类,但是Maybe
等待类型*
返回种类*
。 为了澄清,让我们来看看GHCI的:kind
命令可以告诉我们:
Prelude> :kind Maybe Int
Maybe Int :: *
Prelude> :kind Maybe
Maybe :: * -> *
使用列表它是相同的:
Prelude> :k [String]
[String] :: *
Prelude> :k []
[] :: * -> *
怎么样Either
?
Prelude> :k Either Int String
Either Int String :: *
Prelude> :k Either Int
Either Int :: * -> *
你可以想到直观地将Either
视为一个带参数的函数,但参数是类型 :
Prelude> :k Either Int
Either Int :: * -> *
表示Either Int
正在等待类型参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.