在这里,“值”,“类型”和“种类”具有形式上的含义,因此,考虑它们的通用英语用法或对汽车进行分类的类比,只会使您到目前为止。
我的回答与在Haskell上下文中这些术语的形式含义有关。这些含义基于(尽管并不完全相同)数学/ CS“类型理论”中使用的含义。因此,这将不是一个很好的“计算机科学”答案,但是它可以作为一个很好的Haskell答案。
在Haskell(和其他语言)中,将一种类型分配给一个程序表达式是有帮助的,该程序表达式描述了允许该表达式具有的值的类别。我这里假设你已经看到了足够的例子来了解为什么它会是知道的是,在表达的sqrt (a**2 + b**2)
,变量a
和b
将永远是类型的值Double
,而不是,说,String
和Bool
分别。基本上,拥有类型可以帮助我们编写可在各种值上正常工作的表达式/程序。
现在,您可能尚未意识到的是Haskell类型,例如出现在类型签名中的类型:
fmap :: Functor f => (a -> b) -> f a -> f b
实际上是用类型级别的Haskell子语言编写的。程序文本Functor f => (a -> b) -> f a -> f b
-从字面上看-是用此子语言编写的类型表达式。子语言包括运营商(例如,->
在该语言中的右结合缀运算符),变量(例如,f
,a
,和b
)一种类型的表达,和“应用”到另一个(例如,f a
被f
施加到a
)。
我是否提到在许多语言中为程序表达式分配类型以描述表达式值的类有什么帮助?好吧,在这种类型级别的子语言中,表达式对类型求值(而不是value),并且最终有助于将类型分配给类型表达式以描述允许它们表示的类型的类别。基本上,拥有种类可以帮助我们编写可在各种类型上正常工作的类型表达式。
因此,值对类型就像类型对种,类型帮助我们编写价值级别的程序,而类型则帮助我们编写类型级别的程序。
做这些种什么样子的?好吧,考虑类型签名:
id :: a -> a
如果类型表达式a -> a
是有效的,什么样的的类型,我们应该允许变量a
是?好了,类型表达式:
Int -> Int
Bool -> Bool
看起来有效,因此类型 Int
和Bool
显然是正确的类型。但更复杂的类型如:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
看起来很有效。实际上,由于我们应该能够调用id
函数,甚至:
(a -> a) -> (a -> a)
看起来不错。所以Int
,Bool
,[Double]
,Maybe [(Double,Int)]
,和a -> a
看起来都像类型正确的那种。
换句话说,似乎只有一种,我们称其*
为Unix通配符,并且每种类型都有相同的种类 *
,故事的结尾。
对?
好吧,不完全是。事实证明,Maybe
就其本身而言,类型表达式与Maybe Int
(完全一样sqrt
,就其本身而言,与值表达式一样有效sqrt 25
)一样有效。 但是,以下类型表达式无效:
Maybe -> Maybe
因为,虽然Maybe
是一种类型的表达,它并不代表样的的类型,可以有值。所以,这就是我们应该如何定义*
-这是样的的类型具有值; 它包括“完整”类型,例如Double
,Maybe [(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的类型系统已经充满了悖论,因此这并不重要。