Answers:
好吧,让我们一个接一个地走。
值是程序评估和处理的具体数据。没什么好看的,有些例子可能是
1
true
"fizz buzz foo bar"
对类型的一个很好的描述是“值的分类器”。类型是关于在运行时该值将是什么的一点信息,但是在编译时指示。
例如,如果你告诉我,e : bool
在编译的时候,我就知道,e
要么是true
或false
运行期间,没有别的!因为类型可以很好地对值进行分类,所以我们可以使用此信息来确定程序的一些基本属性。
例如,如果我看到您添加e
以及e'
何时e : int
和e' : String
,那么我知道有些问题了!实际上,我可以对此进行标记并在编译时抛出错误,说:“嘿,那根本没有任何意义!”。
功能更强大的类型系统允许分类更多有趣值的更多有趣类型。例如,让我们考虑一些功能
f = fun x -> x
很显然f : Something -> Something
,但是那应该Something
是什么?在无聊的类型系统中,我们必须指定任意内容,例如Something = int
。在更灵活的类型系统中,我们可以说
f : forall a. a -> a
就是说“对于任何a
,都f
将一个映射a
到a
”。这让我们f
更广泛地使用并编写更多有趣的程序。
而且,编译器将检查实际上是否满足我们给定的分类器,如果f = fun x -> true
存在错误,编译器会说!
因此,作为一个tldr;类型是对表达式在运行时可能具有的值的编译时约束。
一些类型是相关的。例如,整数列表与字符串列表非常相似。这几乎就像sort
整数如何像sort
字符串一样。我们可以想象出一种工厂,通过概括它们之间的差异并根据需要构建它们,从而构建这些几乎相同的类型。这就是类型构造函数。它有点像从类型到类型的函数,但有更多限制。
经典示例是通用列表。类型构造器只是通用定义
data List a = Cons a (List a) | Nil
现在List
是一个将类型映射a
到该类型的值列表的函数!在Java领域,我认为这些可能称为“泛型类”
类型参数只是传递给类型构造函数(或函数)的类型。就像在值级别中一样,我们要说foo(a)
有一个参数a
,就像List a
有类型参数一样a
。
种类有点棘手。基本思想是某些类型是相似的。例如,我们在java int
,...中都有所有原始类型char
,float
它们的行为都好像它们具有相同的“类型”。例外,当我们谈论类型本身的分类器时,我们将分类器称为种。所以int : Prim
,String : Box
,List : Boxed -> Boxed
。
该系统给出了关于可以在哪里使用哪种类型的好规则,就像类型如何控制值一样。说显然是胡说八道
List<List>
要么
List<int>
在Java中,List
需要像这样使用一个具体的类型!如果我们看一下它们的种类List : Boxed -> Boxed
,因为Boxed -> Boxed /= Boxed
,以上是一种错误!
大多数时候,我们并不是真正地考虑种类,而只是将它们视为“常识”,但是对于更高级的类型系统来说,思考是很重要的。
到目前为止,我一直在说些什么
value : type : kind : ...
true : bool : Prim : ...
new F() : Foo : Boxed : ...
如果您对这种事情感兴趣,我强烈建议您投资购买一本好教科书。一般而言,类型理论和PLT非常广泛,并且没有(或至少我)一个连贯的知识基础,您就可以在几个月之内流连忘返。
我最喜欢的两本书是
这两本书都是出色的书,介绍了我刚刚谈论的内容,并且以美丽,详尽的细节进行了介绍。
int
,Java中的类型由一组2 ^ 64个不同的值组成。与子集的类比在涉及子类型时会中断,但这是足够好的初始直觉,尤其是在考虑代数数据类型后(例如,两种类型的并集可以包含任何一种类型的成员;这是那些集的并集) 。
这些东西如何正确定义?
它们由严格的学术数学支持正确地定义,就它们是什么,它们如何工作以及可以保证什么提供了有力的断言。
但是程序员基本上不需要知道这一点。他们需要了解概念。
让我们从价值开始,因为一切都是从那里建立的。值是计算中使用的数据。根据方法的不同,它们是每个人都熟悉的价值观:42、3.14,“如何现在变成棕色母牛”,Jenny down在Accounting中的人事记录等。
值的其他解释是符号。大多数程序员将这些符号理解为枚举的“值”。Left
和Right
是枚举的符号Handedness
(忽略灵巧的人和鱼)。
无论采用何种实现方式,值都是语言在执行计算时所要使用的不同内容。
值的问题在于,并非所有计算对于所有值都是合法的。42 + goat
根本没有道理。
这就是类型发挥作用的地方。类型是定义值子集的元数据。Handedness
上面的枚举是一个很好的例子。此类型表示“仅Left
并且Right
可以在此处使用”。这使程序可以很早地确定某些操作将导致错误。
需要考虑的另一个实际用途是,在后台,计算机使用字节进行工作。字节42可能意味着数字42,或者可能意味着字符*,或者可能意味着Accounting的Jenny。类型(在实际使用中,理论上也没那么多)有助于定义计算机使用的底层字节集的编码。
这是我们开始进行一些尝试的地方。那么,当一种编程语言的变量引用一个类型时,它具有什么类型?
例如,在Java和C#中,它具有类型Type
(具有类型Type
,具有...依此类推)。这是种类背后的概念。在某些语言中,使用Type变量可以比Java和C#做更多有用的事情。一旦出现这种情况可以说成为有用的“我要的值是一个类型,但也有一些样的的IEnumerable<int>
”。- 种。
大多数程序员可以想到类似Java和C#通用约束的类型。考虑一下public class Foo<T> where T: IComparable{}
。在多种语言中,T: kindOf(IComparable)
变量声明变得合法;您不仅可以在类和函数声明中执行特殊操作。
也许并不奇怪,类型构造函数只是类型的构造函数。“但你如何构建一个类型?类型只是有。” 嗯...没那么多。
同样也不足为奇的是,构建任何计算机程序都将使用的所有不同的有用值子集非常困难。类型构造函数有助于程序员以有意义的方式“构建”那些子集。
类型构造函数最普遍的示例是数组定义:int[4]
。在这里,您要指定4
类型构造函数,该构造函数使用该值为您构建一个int
包含4个条目的s 数组。如果您指定了其他输入类型,则将获得其他输出类型。
泛型是类型构造函数的另一种形式,采用另一种类型作为其输入。
在许多语言中,有一个类型构造函数,例如P -> R
要构建一个表示表示采用type P
并返回type 的函数的类型R
。
现在,上下文将确定“返回类型的函数”是否为类型构造函数。根据我的经验(有限),这一行是“您可以在编译时使用此类型吗?”。是?类型构造函数。没有?只是一个功能。
您还记得传递给类型构造函数的参数吗?它们通常称为类型参数,因为类型构造函数的常见形式是Type[param]
或Type<param>
。
*
,而类型构造函数(带有一个参数)具有kind * -> *
。诸如(Num a) => a
(表示“ 作为类型类a
实例的任何Num
类型”)的约束本身并不是种类。类型类Num
本身不是“种类”,但具有种类* -> Constraint
。我发现很难将Haskell的“种类”(我认为与类型理论中的种类紧密相关?)的思想与您给出的例子联系起来。
:kind
命令给出了Num
as 的种类* -> Constraint
。我不知道那可能是GHC特有的。