Coursera Programming Languages, Part A 华盛顿大学 Week 4
Week 4 我们主要介绍 type inference 和 module 系统。
What is type inference?
对于 静态类型语言 (Statically typed languages) 如 ML,Java 与 C,每一个 binding 的类型是在编译时确定的。type-checker 在编译时决定是否要运行整个程序。与之不同的是动态类型语言 (Dynamically typed languages) 例如 Racket,Ruby 与 Python。
ML 语言又和其他静态类型语言不同,它是 隐式类型的 (implicitly typed),意味着编写程序时我们很少需要直接声明 bindings 的类型,type-checker 推断出我们想要的类型。当某些函数的参数或者返回值可以是任何一种类型时,type-checker 会向它们分配多态类 'a, 'b, 'c... 这体现了 ML 语言类型推断与参数化多态 (parametric polymorphism) 的融合。
Overview of the type inference
- It determines the types of bindings in order. 使用之前的 binding types 来推断之后的 binding types
- 对于每个
fun
val
bindings, 分析一些 necessary fact 来决定 binding 的类型。例如,当我们发现表达 x+1 时,可以确认 x 一定是 int type。 - 对于任何 函数参数或是返回值中的 unconstrained types,分配多态类型 'a, 'b, 'c...
- 执行 value restriction
- 若不存在某一种 solution,不执行该程序并抛出 type error
程序的具体分析过程见 section4sum-overview of ML type inference
整个过程有点像解方程未知数,一行一行分析逐个将未知量求出,最后还剩的未知量即分配多态类型
关于 value restriction
当 可变类型 reference 与 多态类结合时,存在一种情况能够破坏我们目前已知的 type inference 系统。
val r = ref NONE
(* a val binding : r has the type 'a option reference *)
val _ = r := SOME "hi"
(* type check operator := : instantiate (but do not change) 'a with string, type checked *)
val i = 1 + valOf (!r)
(* type check operator ! : 'a option reference => 'a option, type check func valOf : 'a option => 'a, instantiate 'a with int, type checked *)
(* though the above 3 lines all type checked, it use + to add a int and a string *)
为了解决这个问题,ML 采用了 value restriction 的方法:只有 val x = e 中的 e 是一个 value 或者是一个 variable 时才有可能分配多态类型给 x。在上面的例子中,ref NONE
是一个函数调用,是需要计算的,不属于 value 或 variable,因此系统拒绝给 r 分配多态类,取而代之的是所谓的 "dummy type : ?X1 option ref" 而不是一个类型变量。dummy type 在接下来的语句中将不能 type check。
value restriction 的存在解决了以上这个问题,但同时也使得某些 partial application 与 currying 的结合无法实现。
Mutual recursion
使用 and
关键字来链接需要相互调用的函数。
Modules for namespace management
一个好的程序应当是模块化的。我们将所有的 bindings 按照其功能分成不同的类并归于对应的 namespace module。这使得程序更加模块化并且能够避免由于变量名称相同引起的 shadowing。
我们用关键字 structure
来定义一个包含众多 bindings 的 module。格式如下 structure ModuleName = struct bindings end
module 内部同样遵循 lexical scope 等规则。在 module 外部需要调用内部的 binding b 时,使用 ModuleName.b 的形式。
Signatures
目前为止,structures 提供了 namespace management 的能力,这非常有用,但是本质上并没有改变程序的功能。
如果我们赋予 structure 签名 (signature, which are types for module),可以对模组外部代码调用模组内部模块是提供严格的限制,也就是说,我们可以创造 structure 的接口 (interface)。
这对我们实现抽象化数据结构等功能十分有用。
如以下代码:
signature MATHLIB =
sig
val fact : int -> int
val half_pi : real
val doubler : int -> int
end
structure MyMathLib :> MATHLIB =
struct
fun fact x =
if x=0
then 1
else x * fact (x - 1)
val half_pi = Math.pi / 2.0
fun doubler y = y + y
end
因为 :> MATHLIB
, 只有当 structure MyMathLib 正确提供了 signature MATHLIB 中声明的所有内容时才能 type check。MATHLIB 还可以声明各种 datatype,exception 等内容。
Use signature to create private parts
在模组外部的代码无论如何也不能调用模组内部未被 signature 声明的内容。这一点可以很好的运用在抽象数据结构的实现中。我们只将用户需要调用的一些函数做成接口,其余的实现可以全部隐藏,这样就能大大减少出现用户进行不合法调用的可能性。
我们要确保 signature 一定提供所有用户所需要的信息:在此基础上,尽量减少要提供的信息量。
Rules for signature matching
structure NAME match signature BLAH :
- 对于 BLAH 中声明的每个 val-binding,NAME 中必须有相同 type 或者 more general type 的对应 binding
- 对于 BLAH 中声明的每个非抽象的 type-binding,NAME 必须有相同的 type-binding
- 对于 BLAH 中声明的每个抽象 type-binding, NAME 必须创建对应的 type (通过 datatype binding or type synonym)
- 注意 NAME 可以含有 BLAH 中未声明的 binding,只是这些 binding 不能在外部被调用