参考:(Programming in Standard ML, chapt 10)
10.1. Datatype Declarations
ML提供datatype定义来为程序员提供自定义的递归的类型。type定义的作用是提供类型的缩写,将type的名称与其定义替换并不会影响程序的功能。而datatype定义则用于引入新的类型:不同于其他任何类型,并且不仅仅代表某个其它类型。正是这种机制使得ML的类型系统具有可扩展性。
一个datatype定义包含:
1)一个或多个类型构造符:可以是(或不是)相互递归的;
2)每个类型构造符可以有一个或多个值构造符。
类型构造器可以有0或多个参数;每个值构造器也可以有0或多个参数。没有参数的值构造符相当于一个常量。如果一个datatype被重新定义,则旧的定义被隐藏。
10.2 Non-Recursive Datatypes(非递归的datatype)
下面的例子是一个无参数、有四个值构造器的类型构造器:
datatype season = Spring | Summer | Autumn | Winter
则类型season只能取四种值: Spring / Summer / Autumn / Winter。习惯上值构造符的首字母大写。给定以上定义,我们可以定义作用于其上的函数,并使用case分析根据构造符的类型进行分类讨论,例子:
fun ordinal Spring = 1 | ordinal Summer = 2 | ordinal Autumn = 3 | ordinal Winter = 4
定义了一个类型为season -> int的函数。
Data type可以被某个类型参数化。例如: datatype 'a option = NONE | SOME of 'a
10.3 Recursive Datatypes
更一般化的是带有递归的类型定义。例如,我们可以在定义二叉树类型tree时使用tree类型的值定义节点:
datatype 'a tree = Empty | Node of 'a tree * 'a * 'a tree
(这里将两个子树都是Empty的二叉树看作叶子节点。节点的形式:为Node (tree 1, val, tree 2))在这种类型上可以定义递归函数,如计算树高度:
fun height Empty = 0 | height (Node (lft, , rht)) = 1 + max (height lft, height rht)
这里用到的是 structural induction。
下面的例子是一种构造有不定个数子树的树结构的一种方法:
datatype 'a tree = Empty | Node of 'a * 'a forest and 'a forest = None | Tree of 'a tree * 'a forest
注意上面定义了两种数据类型:tree 和 forest。并且两者之间是递归定义的。因此两者的定义之间用“and”连接。而相互递归的函数的定义也与此相似。例如计算节点:
fun size_tree Empty = 0 | size tree (Node (_, f)) = 1 + size_forest f and size_forest None = 0 | size forest (Tree (t, f')) = size_tree t + size_forest f'
10.4 Heterogeneous(多相的) Data Structures
在上面的例子中,树的每个节点Node的数据类型都是相同的,例如都是string类型、或都是int类型。通常我们并不需要多相的数据类型(例如一个节点中的数据既可以是string又可以是int),然而在少数情况下也许有这种需要。如何实现多相呢?
考虑其应用场景。那么对多相的数据类型的操作会根据其确切的类型有所不同,因此我们会需要在运行时判断其确切类型(例如对于整数要进行加减,对字符串进行合并等)。因此这样的数据应当有附加的信息来判断它到底属于那个类型。并且我们还应当能够获得这种类型下的数据元本身,以进行具体操作。
自然地,可以应用datatype定义到这种场景。例如:
datatype int_or_string = Int of int | String of string
10.5 Abstract Syntax
datatype定义和模式匹配对于定义和操作一个语言的抽象语法是极其有利的。这种方式的描述十分具有可读性。而且这样构造的编译器更方便进行错误检查。