Haskell中的那种“*”究竟是什么?

时间:2014-11-23 22:15:48

标签: haskell ghc type-kinds

在Haskell中,(值级)表达式被分类为类型,可以用::表示,如下所示:3 :: Int"Hello" :: String,{ {1}}。同样,类型分为。在GHCi中,您可以使用命令(+ 1) :: Num a => a -> a:kind检查类型表达式的类型:

:k

有些定义浮现在> :k Int Int :: * > :k Maybe Maybe :: * -> * > :k Either Either :: * -> * -> * > :k Num Num :: * -> Constraint > :k Monad Monad :: (* -> *) -> Constraint 之类的"具体类型"或者"价值观"或者"运行时值。"例如,请参阅Learn You A Haskell。那是真的吗?我们已经通过a few questions about kinds来解决这个话题,但对*进行规范而准确的解释会很高兴。

<{1}}是什么意思?它与其他更复杂的类型有什么关系?

此外,**扩展名会改变答案吗?

3 个答案:

答案 0 :(得分:30)

首先,*不是通配符!它通常也发音为明星。&#34;

流血边注释:截至2015年2月a proposal to simplify GHC's subkind system (in 7.12 or later)。该页面对GHC 7.8 / 7.10故事进行了很好的讨论。展望未来,GHC可能会使用* :: *来消除类型和种类之间的区别。请参阅Weirich, Hsu, and Eisenberg, System FC with Explicit Kind Equality

标准:类型表达式的描述。

Haskell 98报告defines * in this context as

  

符号*表示所有nullary类型构造函数的种类。

在这种情况下,&#34; nullary&#34;只是意味着构造函数不带参数。 Either是二进制的;它可以应用于两个参数:Either a bMaybe是一元的;它可以应用于一个参数:Maybe aInt是无效的;它可以应用于 no 参数。

这个定义本身有点不完整。包含完全应用的一元,二元等类型构造函数的表达式也具有类*,例如Maybe Int :: *

在GHC中:包含值的东西?

如果我们围绕GHC文档进行讨论,我们会更接近&#34;可以包含运行时值&#34;定义。 GHC Commentary page "Kinds"表示&#34;&#39; *&#39;就是那种盒装价值观。 IntMaybe Float之类的内容有*种类。&#34;另一方面,GHC user's guide for version 7.4.1表示*是&#34;提升类型&#34;。 (当修改该部分时,该段落未被​​保留 PolyKinds。)

盒装价值和提升类型略有不同。根据{{​​3}},

  

如果类型的表示不是指针,则取消装箱。未装箱的类型也没有提升。

     

如果类型有底部作为元素,则提升类型。闭包始终具有提升的类型:即,Core中的任何出租标识符必须具有提升类型。在操作上,抬起的物体是可以进入的物体。只有提升类型可以与类型变量统一。

所以ByteArray#,原始内存块的类型是盒装,因为它表示为指针,但是 unlifted 因为底部不是元素

> undefined :: ByteArray#
Error: Kind incompatibility when matching types:
   a0 :: *
   ByteArray# :: #

因此,旧用户指南定义似乎比GHC评论更准确: *提升类型。(相反,#未提升的类型。)

请注意,如果类型*始终被取消,则对于任何类型t :: *,您都可以构建一个&#34;值&#34;使用undefined :: t或其他一些机制来创建底部。因此,甚至在逻辑上无人居住&#34;像Void这样的类型可以有一个值,即底部。

所以看来,是的,*表示可以包含运行时值的类型,如果undefined是您对运行时值的想法。 (这不是一个完全疯狂的想法,我不会这么想。)

GHC扩展?

有几种扩展使这种类型系统更加生动。其中一些是平凡的:KindSignatures让我们编写种注释,就像输入注释一样。

ConstraintKinds添加Constraint种,大致是=>左侧的那种。

DataKinds让我们介绍除*#之外的新类型,就像我们可以使用datanewtype和{{1}引入新类型一样}}

使用type每个DataKinds声明(条款和条件可能适用)生成促销类型声明。所以

data

引入了通常的值构造函数和类型名称;此外,它还会生成新的 data Bool = True | False 和两种类型:BoolTrue :: Bool

False :: Bool介绍种类变量。这只是一种说法&#34;适用于任何类型的PolyKinds&#34;就像我们说的那样#34;对于任何类型k&#34;在类型级别。关于我们的朋友t以及它是否仍然意味着&#34;类型值为#34;我想你可以说*类型t :: k是一种类型变量可能包含值kk ~ *

答案 1 :(得分:15)

在种类语言的最基本形式中,只有种类*和种类构造函数->,那么*就是那种可以存在的东西。与价值的关系类型;没有别的东西可以是一种价值观。

存在用于对值进行分类的类型。为了进行类型检查,具有相同类型的所有值都是可互换的,因此类型检查器只需要关注类型,而不是特定值。所以我们有&#34;价值水平&#34;所有实际值都存在的地方,以及&#34;类型级别&#34;他们的类型住在哪里。 &#34;类型&#34;关系形成两个级别之间的链接,单个类型是(通常)许多值的类型。 Haskell使这两个层次非常明确;这就是为什么你可以在data Foo = Foo Int Chat Bool声明类型级别事物Foo(类型为*的类型)和值级别事物的声明Foo(类型为Int -> Char -> Bool -> Foo的构造函数)。所涉及的两个Foo只是简单地引用不同级别的不同实体,而Haskell将这些实体完全分开,以至于它总能告诉您所指的级别,从而可以允许(有时容易混淆)不同的事情。级别具有相同的名称。

但是一旦我们引入了自己具有结构的类型(比如Maybe Int,这是一个类型构造函数Maybe应用于类型Int),那么我们就有了存在于类型级别实际上并不代表与任何值的类型关系。没有类型仅为Maybe的值,只有类型Maybe Int(以及Maybe BoolMaybe (),甚至Maybe Void等的值)。因此,我们需要对类型级别的事物进行分类,原因与我们需要对值进行分类的原因相同;只有某些类型表达式实际上代表了可以作为值类型的东西,但是它们中的许多可以互换地用于&#34;种类检查&#34; (对于价值级别的东西,它是否是正确的类型,它被声明为不同级别的问题类型)。 1

所以*(通常被称为发音&#34;类型&#34;)是基本类型;它是所有类型级别的东西,可以说是值的类型。 Int有价值观;因此它的类型是*Maybe没有值,但它接受一个参数并生成一个具有值的类型;这让我们有点像___ -> *。我们可以通过观察Maybe的参数被用作Just a中出现的值的类型来填补空白,因此它的参数也必须是一种值(带有种类{ {1}}),因此*必须有Maybe种。等等。

当你处理只涉及星星和箭头的种类时,只有种类* -> *的类型表达式是值的类型。任何其他类型(例如*)仅包含其他&#34;类型级实体&#34;这不是包含值的实际类型。

* -> (* -> * -> *) -> (* -> *),据我所知,并没有真正改变这种情况。它只允许您在类型级别进行多态声明,这意味着它将变量添加到我们的种类语言中(除了星号和箭头)。所以现在我可以考虑类型级别的事物PolyKinds;这可以实例化为k -> ** -> *(* -> *) -> *。我们获得了与类型级别(* -> (* -> *)) -> *获得的完全相同的权力;我们可以用一个包含变量的类型编写一个(a -> b) -> [a] -> [b]函数,而不必分别编写每个可能的map函数。但是仍然只有一种类型包含类型值的类型:map

*还为善意的语言引入了新的东西。实际上它的作用是让我们声明任意新类型,它包含新的类型级实体(正如普通的DataKinds声明允许我们声明包含新的值级实体的任意新类型)。但它并没有让我们通过所有3个层面的实体对应来宣布事物;如果我有data并使用data Nat :: Z | S Nat将其提升到种类级别,那么我们会在类型级别上存在两个名为DataKinds的不同内容(作为值级别的类型{ {1}},NatZ等),以及类型级别(类型级别 S Z的种类,{{1} },S (S Z))。 类型级别 Z不是任何值的类型; S Z位于类型级 S (S Z)(反过来属于Z),而不是类型级别Z。所以Nat将新的用户定义的东西添加到种类语言中,这种类型语言可以是类型级别的新用户定义的东西,但仍然是唯一可以是类型的类型级别的东西。价值属于*

我所知道的真正改变了这种语言的唯一补充是@ ChristianConkle的回答中提到的那种,例如Z(我相信有一对现在也更多了?我对{&lt; {低级&#34;类型,例如DataKinds)并不是非常了解。这些类型具有GHC需要知道的以不同方式处理的值(例如,不假设它们可以被盒装和延迟评估),即使涉及多态函数,因此我们不能仅仅附加知识他们需要以不同的方式对待这些价值观。类型,或者在调用多态函数时会丢失。


1 单词&#34; type&#34;因此可能有点混乱。有时,它用于指代实际与价值层面上的事物建立关系的事物(这是当人们说&#34时使用的解释; *不是一个类型,它是一个类型构造函数&#34;)。有时它曾经用于指代类型级别存在的任何东西(在此解释#下实际上是一种类型)。在这篇文章中,我试图非常明确地引用&#34;类型级别的东西&#34;而不是使用&#34; type&#34;作为一个简写。

答案 2 :(得分:6)

对于尝试了解种类的初学者(您可以将其视为某种类型)我推荐Learn you a Haskell本书的这一章。

我个人认为这种方式:

你有具体的类型,例如IntBoolString[Int]Maybe IntEither Int String

所有这些都有*种类。为什么?因为他们不能再将任何类型作为参数; IntInt; Maybe IntMaybe Int。那么Maybe[]Either呢?

当您说Maybe时,您没有具体类型,因为您没有指定其参数。 Maybe IntMaybe String不同,但两者都有*种类,但Maybe正在等待某种类型*返回*:kind }。为了澄清,让我们来看看GHCI的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

表示var last_call = new Date(); function NoConcurrExecFunc(param_a, param_b) { var new_date = new Date(); var min_interval = 750; if ((last_call - new_date) < 0) { last_call = new Date(new_date.getTime() + min_interval); // Do your stuff! } else { setTimeout(function () { NoConcurrExecFunc(param_a, param_b); }, min_interval); } } 正在等待类型参数。