类型和种类有什么区别?


26

我正在学习编程语言Haskell,并且试图围绕a type和a 之间的区别进行思考kind

据我了解,a kind is a type of type。例如a ford is a type of cara car is a kind of vehicle

这是思考这个问题的好方法吗?

因为,我的大脑当前的连接方式是a ford is a **type** of car,但同时需要car is a **type** of vehiclea car is a **kind** of vehicle。即条款,type并且kind可以互换。

有人能对此有所启示吗?


6
我只是从导致该讨论的Stack Overflow帖子中来到这里的。我不确定我是否有资格详细回答-但您肯定对“类型”和“种类”一词过于直白,试图将它们与英语的含义联系起来(实际上,它们是同义词)。您应该将它们视为技术术语。我认为,“类型”是所有程序员都很好理解的,因为该概念对于每种语言都是至关重要的,即使是Java这类弱类型的语言也是如此,“种类”是Haskell中用于“类型的类型”的技术术语。这就是全部。
罗宾·齐格蒙德

2
@RobinZigmond:您说对了,因为这是技术术语,但它们的使用范围不仅限于Haskell。也许反向链接到引发这个问题的Stack Overflow讨论?
Andrej Bauer

@AndrejBauer我从没说过在Haskell之外没有使用过它们,当然,正如我所说,“ type”实际上在每种语言中都使用过。我从没真正在Haskell之外碰到过“种类”,但是Haskell是我所知道的唯一功能语言,而且我小心不要说这个词在其他地方没有用,只是说它在哈斯克尔。(和链接,根据您的请求,在这里
Robin Zigmond

ML系列语言也有多种,例如Standard ML和OCaml。我认为这些名字并没有明确地显示出来。它们表现为签名,其元素称为结构
Andrej Bauer

1
准确的英语比喻是:福特是汽车的一种,汽车是汽车的一种,但是汽车和汽车都是同一种类型:名词。红色是汽车颜色的一种,而RPM是汽车性能指标的一种,两者属于同一类:形容词。
slebetman

Answers:


32

在这里,“值”,“类型”和“种类”具有形式上的含义,因此,考虑它们的通用英语用法或对汽车进行分类的类比,只会使您到目前为止。

我的回答与在Haskell上下文中这些术语的形式含义有关。这些含义基于(尽管并不完全相同)数学/ CS“类型理论”中使用的含义。因此,这将不是一个很好的“计算机科学”答案,但是它可以作为一个很好的Haskell答案。

在Haskell(和其他语言)中,将一种类型分配给一个程序表达式是有帮助的,该程序表达式描述了允许该表达式具有的的类别。我这里假设你已经看到了足够的例子来了解为什么它会是知道的是,在表达的sqrt (a**2 + b**2),变量ab将永远是类型的值Double,而不是,说,StringBool分别。基本上,拥有类型可以帮助我们编写可在各种值上正常工作的表达式/程序。

现在,您可能尚未意识到的是Haskell类型,例如出现在类型签名中的类型:

fmap :: Functor f => (a -> b) -> f a -> f b

实际上是用类型级别的Haskell子语言编写的。程序文本Functor f => (a -> b) -> f a -> f b-从字面上看-是用此子语言编写的类型表达式。子语言包括运营商(例如,->在该语言中的右结合缀运算符),变量(例如,fa,和b)一种类型的表达,和“应用”到另一个(例如,f af施加到a)。

我是否提到在许多语言中为程序表达式分配类型以描述表达式值的类有什么帮助?好吧,在这种类型级别的子语言中,表达式对类型(而不是value),并且最终有助于将类型分配给类型表达式以描述允许它们表示的类型的类别。基本上,拥有种类可以帮助我们编写可在各种类型上正常工作的类型表达式。

因此,类型就像类型类型帮助我们编写价值级别的程序,而类型则帮助我们编写类型级别的程序。

做这些什么样子的?好吧,考虑类型签名:

id :: a -> a

如果类型表达式a -> a是有效的,什么样的类型,我们应该允许变量a是?好了,类型表达式:

Int -> Int
Bool -> Bool

看起来有效,因此类型 IntBool显然是正确的类型。但更复杂的类型如:

[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]

看起来很有效。实际上,由于我们应该能够调用id函数,甚至:

(a -> a) -> (a -> a)

看起来不错。所以IntBool[Double]Maybe [(Double,Int)],和a -> a看起来都像类型正确的那种

换句话说,似乎只有一种,我们称其*为Unix通配符,并且每种类型都有相同的种类 *,故事的结尾。

对?

好吧,不完全是。事实证明,Maybe就其本身而言,类型表达式与Maybe Int(完全一样sqrt,就其本身而言,与值表达式一样有效sqrt 25)一样有效。 但是,以下类型表达式无效:

Maybe -> Maybe

因为,虽然Maybe是一种类型的表达,它并不代表样的类型,可以有值。所以,这就是我们应该如何定义*-这是样的类型具有值; 它包括“完整”类型,例如DoubleMaybe [(Double,Int)]但不包括不完整无价值的类型,例如Either String。为简单起见,我将这些类型的完整类型称为*“具体类型”,尽管该术语不是通用的,并且“具体类型”可能意味着与C ++程序员非常不同的东西。

现在,在类型表达式中a -> a,只要类型a具有种类 *(具体类型的种类),类型表达式的结果a -> a将具有种类(即具体类型的种类)。 *

那么,什么样的类型Maybe?好了,Maybe可以应用于一种混凝土类型,以产生另一种混凝土类型。因此,Maybe看起来有点像一个类型级别的函数,该函数接受类型类型 *并返回类型类型 *。如果我们有这样的花了一个值水平函数类型 Int和返回类型 Int,我们想给它一个类型的签名Int -> Int,所以通过类比,我们应该给Maybe一个样的签名* -> *。GHCi同意:

> :kind Maybe
Maybe :: * -> *

回到:

fmap :: Functor f => (a -> b) -> f a -> f b

在这种类型的签名,变量f有一种* -> *和变量a,并b有样*; 内置运算符->具有kind * -> * -> *(它*在左侧需要一个type,在右侧需要一个type,并且还返回一个type *)。从这个善良的推理规则,你可以推断出a -> b是一个有效的类型与种类*f a并且f b也有效类型的一种*,并且(a -> b) -> f a -> f b是一种有效的类型*

换句话说,编译器可以“类型检查”类型表达式(a -> b) -> f a -> f b以验证其对正确类型的变量是否有效,就像“类型检查” sqrt (a**2 + b**2)以验证其对正确类型的变量有效一样。

对“类型”和“种类”使用单独的术语的原因(即,不谈论“类型的类型”)主要是为了避免混淆。上面的种类看起来与类型完全不同,并且至少在一开始似乎表现出很大的不同。(例如,它需要一些时间来环绕,每一个“正常”的类型有同种想法你的头*和那种a -> b*不是* -> *。)

其中一些也是历史性的。随着GHC Haskell的发展,值,类型和种类之间的区别开始变得模糊。如今,可以将值“提升”为类型,而类型和种类实际上是同一回事。因此,在现代的Haskell中,值(几乎)都具有类型和ARE类型,而类型的种类仅仅是更多类型。

@ user21820要求对“类型和种类确实是同一件事”进行一些补充说明。更清楚一点,在现代GHC Haskell(我认为是从8.0.1版本开始)中,类型和种类在大多数编译器代码中都得到统一处理。编译器会在错误消息中做出一些努力,以区分是“类型”还是“种类”,这取决于编译器分别抱怨值的类型还是类型的类型。

另外,如果未启用任何扩展名,则可以使用表面语言轻松区分它们。例如,类型(值)在语法中具有表示形式(例如,在类型签名中),但是(我认为)类型(类型)是完全隐式的,并且在它们出现的地方没有显式语法。

但是,如果您打开适当的扩展名,类型和种类之间的区别就会消失。例如:

{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
  Bar :: Bool -> * -> Foo

在这里,Bar(既是值又是)类型。作为类型,它的种类是Bool -> * -> Foo,这是一个类型级别的函数,它接受一个种类的类型Bool(既是一个种类,又是一个种类)和一个种类,*并产生一个种类Foo。所以:

type MyBar = Bar True Int

正确进行种类检查。

正如@AndrejBauer在他的回答中所解释的那样,未能区分类型和种类是不安全的-具有*类型/种类本身(在现代Haskell中就是这种情况)的类型/种类会导致自相矛盾。但是,由于未终止,Haskell的类型系统已经充满了悖论,因此这并不重要。


如果“类型和种类确实是同一回事”,则类型本身type就是type本身,根本不需要kind。那么区别到底是什么?
user21820

1
@ user21820,我在末尾添加了一条注释,可能会解决此问题。简短的回答:现代GHC Haskell并没有真正的区别。
KA Buhr

1
这是一个很好的答案-非常感谢您的分享。它写得很好,并逐步介绍了一些概念-几年来没有写过Haskell的人,对此深表感谢!
ultrafez

@KABuhr:感谢您的加入!
user21820

19

如果您了解集合论中集合与类之间的区别,那么将问题考虑为可能会有所帮助 如果不是,您可以将种类视为“大”或“高级”类型,其元素可以是类型,也可以以某种方式涉及类型。例如:

type:kind=set:class.

  • Bool 是一种
  • Type 是一种,因为其元素是类型
  • Bool -> Int 是一种
  • Bool -> Type 是一种,因为其元素是返回类型的函数
  • Bool * Int 是一种
  • Bool * Type 是一种,因为其元素与一个组件成对

U0U1U2U0BoolNatNatNatU1U0BoolU0U0U0Un+1UnUn×

U0U1U0U1U0**U_1


2
我认为(GHC)Haskell没有关于宇宙的任何概念。Type :: Type是一个公理。在这种情况下,“类型”和“种类”之间的区别完全是人类语言。True有一个类型,Bool并且Bool有一个类型Type,它本身也有type Type。有时我们将类型称为类型,以强调它是类型级别实体的类型,但是在Haskell中,它仍然只是类型。在宇宙确实存在,像勒柯克的系统,然后 “型”可以指一个宇宙和“种”到另一个,但我们通常希望无限多的宇宙。
HTNW

1
区别不仅是“人类语言”,而且是底层类型系统中的形式区别。Type :: Type在类型和种类之间同时具有区别是很可能的。另外,Type :: TypeHaskell 演示了什么代码?
Andrej Bauer

1
我还应该说,*在Haskell中是各种各样的宇宙。他们只是不这么称呼。
Andrej Bauer

3
@AndrejBauer TypeData.Kinds*应该是同义词。最初,我们只是*作为原始元素,而如今,它GHC.Types.Type在内部模块中内部定义,而在内部模块GHC.Types中则定义为type Type = TYPE LiftedRep。我认为这TYPE是真正的原始类型,提供了一系列的类型(提升类型,非装箱类型...)。这里的大多数“模糊”复杂度是为了支持一些低级优化,而不是出于实际类型理论的原因。

1
我会尝试总结一下。如果v是值,则其类型为v :: T。如果T为类型,则其类型为:T :: K。类型的类型称为其类型。看起来像的类型TYPE rep可能被称为排序,尽管这个词并不常见。当且仅当T :: TYPE repT允许出现在一个等式右边::。“种类”一词具有细微差别:Kin T :: K是一种,但in不是v :: K,尽管它是相同的K。我们可以定义“,K如果它的种类是一种”,也就是“种类在RHS上::”,但这并不能正确地捕获用法。因此我的“人为区分”立场。
HTNW

5

一个就像您坐在车道上的红色2011福特野马,上面有19206英里。

非正式地,该特定值可以有多种类型:它是野马,它是福特,它是汽车,它是车辆,以及许多其他可以组成的类型(“事物”的类型属于您”或“红色事物”的类型,或...)。

(在Haskell中,一阶近似(GADT破坏了此属性,数字文字和OverloadedStrings扩展的魔力使它模糊了一点),值具有一个主要类型,而不是过多的非正式“类型”,您可以将' stang。42是(为了便于说明)Int; Haskell中没有“数字”或“偶数整数”的类型-确切地说,您可以创建一个,但它与Int。是不相交的类型。)

现在,“野马”可能是“汽车” 的子类型 -野马的每个值也都是汽车。但是,类型 -或者,使用Haskell的术语中,那种 “野马”是不是“车”。“ Mustang”类型不是您可以停放在车道上或四处行驶的东西。“ Mustang”是名词,类别或类型。这些都是非正式的各种 “野马”的。

(同样,Haskell对于每个类型级别的事物仅识别一种。因此,Int具有kind *,没有其他种类。Maybe具有kind * -> *,没有其他种类。但是直觉应该仍然成立:42是an Int,您可以Int用它来做点事情就像加法和减法,Int它本身不是一个Int;没有这样的数字Int + Int。您可能会非正式地听到人们说Int是a Num,这表示他们有一个该类型的类型类的实例 —这不是同一件事话说有。有一种“类型”,这在Haskell拼写)。NumIntInt NumInt*

那么,不是每个非正式的“类型”都是名词还是类别?所有类型都具有相同的种类吗?如果太无聊,为什么还要谈论种类呢?

在这里,英语的类比可能会有些摇摆不定,但请允许我:假装英语中的“拥有者”一词没有孤立的意义,而没有描述事物的所有权。假装有人称您为“所有者”,那对您完全没有意义;但是如果有人称您为“车主”,您就可以理解他们的意思。

“所有者”就不会有同样的是“车”,因为你可以谈论一辆车,但你能不能谈谈在英语的本作后续版本的所有者。您只能谈论“车主”。当“所有者”应用于已经具有“名词”种类的东西(例如“汽车”)时,它只会创建“名词”种类的东西。我们可以说“所有者”的种类是“名词->名词”。“所有者”就像一个函数,它接受一个名词并从中产生一个不同的名词。但这本身不是名词。

请注意,“车主”不是“车”的子类型!它不是接受或返回汽车的功能!它只是与“汽车”完全不同的类型。它描述了两条手臂和两条腿的价值,他们在某一时刻拥有一定数量的钱,然后将这些钱带给经销店。它没有描述具有四个轮子和油漆作业的值。另请注意,“汽车所有者”和“狗所有者”是不同的类型,您可能要对其中一个进行的操作可能不适用于另一个。

(同样,当我们在Haskell中说“ Maybe有一种” * -> *时,我们的意思是谈论“ a Maybe” 是荒谬的(正式;非正式地,我们一直在做)。相反,我们可以有a Maybe Int或a Maybe String,因为这些是种类)*

因此,讨论所有类型的全部目的是使我们可以围绕诸如“所有者”之类的词来形式化我们的推理,并强制我们只采用“完全构建”且并非荒谬的类型的值。


1
我并不是说您的比喻是错误的,但我认为这可能会引起混乱。迪克斯特拉(Dijkstra)关于类比有一些话。谷歌“对真正教授计算机科学的残酷对待”。
拉斐尔·卡斯特罗

我的意思是,有汽车类比,然后有汽车类比。我不认为以自然语言来突出隐式类型结构(诚然,我在下半部分对此进行了延伸)作为一种解释形式类型系统的方式,与通过谈论类似的教学方式相似。一个程序“想要”做什么。
user11228628

1

据我了解,一种是一种类型。

是的-所以让我们探究这意味着什么。 Int或者Text是具体的类型,但是Maybe a是一个抽象的类型。直到您确定要为a特定变量(或值/表达式/什么)指定的特定值(例如),它才会成为具体类型Maybe Text

我们说这Maybe a是一个类型构造函数,因为它就像一个接受单个具体类型(例如Text)并返回具体类型(Maybe Text在这种情况下)的函数。但是其他类型构造函数在返回具体类型之前可能会使用更多的“输入参数”。例如,在构造具体类型()之前,Map k v需要采用两种具体类型(例如Int和)。TextMap Int Text

因此,Maybe aList a类型构造函数具有相同的“签名”,我们将其称为* -> *(类似于Haskell函数签名),因为如果给它们一个具体类型,它们将吐出一个具体类型。我们称这种类型为“种类”,Maybe并且List具有相同的种类。

具体类型被称为具有kind *,而我们的Map示例是kind,* -> * -> *因为在输出具体类型之前需要将两个具体类型作为输入。

您可以看到,它几乎只不过是我们传递给类型构造函数的“参数”的数量-但要意识到,我们也可能将类型构造函数嵌套在类型构造函数内部,因此我们最终可以得到一种* -> (* -> *) -> *例如。

如果您是Scala / Java开发人员,则可能还会发现以下说明很有用:https : //www.atlassian.com/blog/archives/scala-types-of-a-higher-kind


这是不正确的。在Haskell中,我们区分类型的Maybe a同义forall a. Maybe a类型*和类型的同义Maybe类型* -> *
b0fh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.