Lexical Scope.

在 ML 中,函数的 evaluation 遵循这样一条铁律 (之前在 function binding 部分中也介绍过):

The body of a function is evaluated in the environment where the function is defined, not the environment where the function is called.

也就是说,函数体的 evaluation 依赖的是函数各个函数 bindings 与被定义时的环境。我们通过一个 silly example 来认识这一点。

1
2
3
4
5
val x = 1
fun f y = x + y
val x = 2
val y = 3
val z = f (x+y)

变量 z 的值是 6,而不是 5。在调用函数 f 的环境中,x 的值为 2,y 的值为 3,因此函数的参数 evaluates to 5。而在 evaluate 函数体时,我们参照的是函数 f 定义时的环境:在那里 x 还未被 shadow,其值为 1。而参数 y 又与 5 绑定,最后计算出来的值为 1 + 5 = 6。

这样的 semantics,就是词法作用域 lexical scope。比较来说,函数体与参数均在调用处的环境被 evaluate 的机制更加符合直觉,这种与 lexical scope 对立的 semantics 称为动态作用域 dynamic scope

早期的编程语言大多是遵循 dynamic scope 的,但是很快程序员就发现了其致命问题:变量的 shadowing 常常会与这一机制产生冲突。一个最典型的例子如下:

1
2
3
4
var x = 3
fun f = x + 1
var x = "hello"
var y = f

这一程序是 type-checked 的:x : int, f : null -> int, x (shadow) : string, y : int;但在 dynamic scope 中,y = 3 + “hello”,这破坏了类型一致性。而在 lexical scope 中这一程序则不会出现问题。

dynamic scope 还会导致许多其他的问题 (见 section3sum),因此现在的绝大多数语言默认都采用 lexical scope 来作为函数 evaluation 的准则。但 dynamic scope 并没有完全消失:

  • 少数语言 (如 Racket) 仍然提供对 dynamic scope 的支持。
  • 一些特殊的编程 feature (如 exception) 遵循的是 dynamic scope。

When an exception is raised, evaluation has to look up which handle expression should be evaluated. This “look up” is done using the dynamic call stack, with no regard for the lexical sturcture of the program.