1. 背景
Standard ML 语言由1983提案(proposed),经过1984年至1988年进行设计(designed),
规范《The Definition of Standard ML》,最终在1990年完成(defined)。
Standard ML '97
是 Standard ML
的简化版,
修订版规范《The Definition of Standard ML (Revised)》于1997年完成。
修订版的 Standard ML '97
有时候也称为 Standard ML
,或者 Standard ML '97
,SML '97
,
为了区分,1990年的 Standard ML
也称为 SML '90
。
2. 模块语言
Standard ML 程序可以划分为独立的单元(unit),程序单元也称为结构(structure)。
结构中包含了一些类型(type)和值(value),
借助签名(signature)可以将多个结构组合成更大的结构,
因此,较大的结构可以按层级划分成不同的子结构(substructure)。
其中,签名可以看做是结构自身的类型(type),
泛型化或参数化的结构,也被称为函子(functor)。
3. 签名和结构
A signature may be thought of as a description of a structure, and a structure may correspondingly be thought of as an implementation of a signature.
签名可以看做是结构的规范描述,结构可以看做是签名的具体实现。
其他语言中也有类似的概念,签名通常被称为接口,或包的规范(package specifications),
结构通常被称为实现(implementation)或包(package)。
而与其他语言不同的是,Standard ML 中的签名和结构之间是多对多关系。
一个签名可以描述多个不同的结构,一个结构可以同时满足多个不同的签名。
3.1 签名
A signature is a specification, or a description, of a program unit, or structure.
签名是一段程序单元(或结构)的规范,或描述。
结构由类型构造器,异常构造器,值的绑定关系构成,
而签名则指定了结构的约束条件(requirement),例如,
结构应当包含哪些类型,包含哪些值,这些值的类型是什么,等等。
一个结构如果满足了这些约束条件(requirement),
我们就说它匹配了(match)或实现了(implement)该签名。
signature QUEUE =
sig
type ’a queue
exception Empty
val empty : ’a queue
val insert : ’a * ’a queue -> ’a queue
val remove : ’a queue -> ’a * ’a queue
end
以上代码定义了一个名为QUEUE
的签名。
匹配该签名的结构,必须满足以下条件:
(1)有一个单参类型构造器,'a queue
,
(2)有一个零参异常,Empty
,
(3)有一个值empty
,它是多态类型的'a queue
,
(4)有两个多态函数,insert
和remove
。
3.2 签名的继承(inheritance)
通过包含(signature inclusion)和特化(signature specialization),
我们可以使用现有的签名,得到另一个新的签名,
这是继承(inheritance)签名的两种形式。
(1)包含
包含用于得到,比已有签名具有更多内容的签名。
signature QUEUE_WITH_EMPTY =
sig
include QUEUE
val is empty : ’a queue -> bool
end
(2)特化
特化用于得到,比已有签名中的类型更具体的签名。
signature QUEUE_AS_LISTS =
QUEUE where type ’a queue = ’a list * ’a list
3.3 结构
结构由类型构造器,异常构造器,值的绑定关系构成。
Structures are implementations of signatures; signatures are the types of structures.
结构实现了签名,签名是结构的类型。
structure Queue =
struct
type ’a queue = ’a list * ’a list
exception Empty
val empty = (nil, nil)
fun insert (x, (b,f)) = (x::b, f)
fun remove (nil, nil) = raise Empty
| remove (bs, nil) = remove (nil, rev bs)
| remove (bs, f::fs) = (f, (bs, fs))
end
以上代码定义一个名为Queue
的结构,它实现了签名QUEUE
。
3.4 匹配(mathing)
我们说一个解构实现了某个签名,指的是,
该结构满足签名中所要求的一切类型定义。
结构中提供的类型构造器,必须与签名中所要求的具有相同数目的参数,
结构中提供的值,必须满足签名中所要求的类型,
结构中提供的异常,其参数类型也必须与签名中所要求的一样。
我们把结构所能满足的最严格(stringent),最精确的(precise)签名,称为它的主签名(principal signature)。
我们说一个结构精确(exactly)匹配(match)到了一个签名上,
如果该签名没有比主签名提供更多的约束条件。
如果签名sigexp_1
,具有sigexp_2
中所有的约束条件,
我们就说,签名sigexp_1
可以匹配签名sigexp_2
。
signature QUEUE =
sig
type ’a queue
exception Empty
val empty : ’a queue
val insert : ’a * ’a queue -> ’a queue
val remove : ’a queue -> ’a * ’a queue
end
signature QUEUE_WITH_EMPTY =
sig
include QUEUE
val is empty : ’a queue -> bool
end
signature QUEUE_AS_LISTS =
QUEUE where type ’a queue = ’a list * ’a list
其中,签名QUEUE_WITH_EMPTY
和QUEUE_AS_LISTS
都可以匹配到QUEUE
上。
3.5 归属(ascription)
签名归属(signature ascription)将一个结构强行指定到一个签名上面,
从而限制了以后该结构的被使用的灵活度。
Standard ML中有两种方式对结构进行归属,
(1)透明或描述性归属(transparent, or descriptive ascription)
structure strid : sigexp = strexp
(2)不透明或限制性的归属(opaque, or restrictive ascription)
structure strid :> sigexp = strexp
透明归属使用冒号:
,不透明归属使用:>
。
这两种归属方式中,确定结构strid
签名的步骤如下,
(1)验证strexp
是否实现了sigexp
,
我们通过strexp
的主签名sigexp_0
,是否可以匹配到sigexp
上来确定。
(2)在匹配过程中,主签名sigexp_0
中,可能包含了比sigexp
中更多的类型,
于是,我们将得到一个sigexp
的增强版sigexp'
(3)对于透明归属,strid
的签名为增强版sigexp'
,
而对于不透明归属,strid
的签名为原版sigexp
。
例子,
(1)不透明归属
signature QUEUE =
sig
type ’a queue
exception Empty
val empty : ’a queue
val insert : ’a * ’a queue -> ’a queue
val remove : ’a queue -> ’a * ’a queue
end
structure Queue :> QUEUE =
struct
type ’a queue = ’a list * ’a list
val empty = (nil, nil)
fun insert (x, (bs, fs)) = (x::bs, fs)
exception Empty
fun remove (nil, nil) = raise Empty
| remove (bs, f::fs) = (f, (bs, fs))
| remove (bs, nil) = remove (nil, rev bs)
end
不透明归属Queue :> QUEUE
保证了类型'a queue
的抽象性,
Queue
可以更改具体实现,而不会影响用户代码,
用户不用关心'a queue
的具体类型。
(2)透明归属
透明归属简化了在签名中显式指定所有类型的工作。
我们总是可以将透明归属,替换成不透明归属,再手动扩充一些类型。
signature ORDERED =
sig
type t
val lt : t * t -> bool
end
structure String : ORDERED =
struct
type t = string
val clt = Char.<
fun lt (s, t) = ... clt ...
end
其中,辅助函数clt
对外是不可见的,
虽然ORDERED
签名中没有指定,String.t
的类型也为string
,
透明归属,根据ORDERED
计算出来了一个增强版的签名,包含了这个类型定义,
所以,String
的真实签名是,
ORDERED where type t = string
4. 子结构
一个子结构就是“结构中的结构”,
signature MY_GEN_DICT =
sig
type key
type ’a dict
val empty : ’a dict
val insert : ’a dict * key * ’a -> ’a dict
end
signature MY_STRING_DICT =
MY_GEN_DICT where type key = string
signature MY_INT_DICT =
MY_GEN_DICT where type key = int
其中,MY_GEN_DICT
是一个一般化的签名,类型key
被抽象出来。
而在签名MY_STRING_DICT
,MY_INT_DICT
中,我们可以指定具体的key
。
除了可以指定签名中具体的类型,还可以指定签名中的具体结构。
signature ORDERED =
sig
type t
val lt : t * t -> bool
val eq : t * t -> bool
end
signature DICT =
sig
structure Key : ORDERED
type ’a dict
val empty : ’a dict
val insert : ’a dict * Key.t * ’a -> ’a dict
val lookup : ’a dict * Key.t -> ’a option
end
signature STRING_DICT =
DICT where type Key.t=string
signature INT_DICT =
DICT where type Key.t=int
以上代码,先定义了一个名为ORDERED
的签名,
然后在签名DICT
中,指定其子结构Key
满足签名ORDERED
。
最后,签名STRING_DICT
和INT_DICT
,通过指定子结构Key
中类型t
的具体值完成实例化。
4. 参数化
为了复用,支持定义泛型化或参数化的模块十分重要,
模块的实现中,留下一部分未完全确定(unspecified)的部分,然后再用不同的方式实例化(instantiated),
那些共同部分,就只需要实现了一次,被所有实例所共享了。
在Standard ML中,这些一般化模块称为函子(functor),
函子是一个模块级别的(module-level)函数,它接受一个结构(structure)作为参数,
然后生成一个结构作为结果。
signature ORDERED =
sig
type t
val lt : t * t -> bool
val eq : t * t -> bool
end
signature DICT =
sig
structure Key : ORDERED
type ’a dict
val empty : ’a dict
val insert : ’a dict * Key.t * ’a -> ’a dict
val lookup : ’a dict * Key.t -> ’a option
end
functor DictFun
(structure K : ORDERED) :>
DICT where type Key.t = K.t =
struct
structure Key : ORDERED = K
datatype ’a dict =
Empty |
Node of ’a dict * Key.t * ’a * ’a dict
val empty = Empty
fun insert (None, k, v) =
Node (Empty, k, v, Empty)
fun lookup (Empty, ) = NONE
| lookup (Node (dl, l, v, dr), k) =
if Key.lt(k, l) then
lookup (dl, k)
else if Key.lt (l, k) then
lookup (dr, k)
else
v
end
DictFun
是一个函子,它接受一个结构K
作为参数,返回了一个结构。
其中K
透明归属于ORDERED
,DictFun
不透明归属于DICT
。
它保证了'a dict
的抽象性(不透明归属),而K.t
则是具体的(透明归属),由函子的参数决定。
函子的调用方式如下,
structure LtIntDict = DictFun (structure K = LessInt)
structure LexStringDict = DictFun (structure K = LexString)
structure DivIntDict = DictFun (structure K = DivInt)
参考
Standard ML '97
Programming in Standard ML