简而言之,词法作用域发生在编译时(或者更准确地说,在评估函数定义时)。此外,词法作用域可以是静态作用域:这就是 ML 语言(SML、OCaml、Haskell)的做法。
环境
每个功能都在某个环境中定义。此外,每个函数都会创建自己的本地环境,嵌套在封闭环境中。顶级环境是定义所有常用变量、函数(+
、-
、sin
、map
等)和语法(与可以扩展语法的语言相关的语言,如 Common Lisp、Scheme、Clojure)的地方。
每个函数创建自己的本地环境,嵌套在封闭环境中(例如,顶层或其他函数)。函数参数和所有局部变量都存在于这个环境中。如果函数引用了未在本地环境中定义的变量或函数(在此环境中称为free),则可以在函数定义的封闭环境中找到它(如果在封闭环境的封闭环境中找不到,则在封闭环境中更高等)上)。这与动态范围不同,其中值将在调用函数的环境中找到。
我将使用 Scheme 来说明这一点:
y
在这个定义中是免费的
(define (foo x)
(+ x y))
这里是y
在顶层环境中定义的
(define y 1)
引入本地'y',但foo
将使用y
来自定义的封闭(顶级)环境。因此,结果是 2 而不是 11。
(let ((y 10))
(foo 1))
=> 2
您还可以定义一个函数(或 Scheme 术语中的一个过程),其中包含一个本地环境:
(define bar
(let ((y 100))
(lambda (x) (+ x y))))
(bar 1)
=> 101
这里的procedure valuebar
被定义为一个procedure。变量y
在过程体中又是自由的。但是封闭环境是由定义为 100的let
表单创建的。因此,当被调用时,它是获取的值而不是顶级值(在动态范围的语言中,它会返回 2)。y
bar
y
回答您的最后一个问题,可以手动创建自己的环境,但工作量太大,可能效率不高。当实现语言(例如,Scheme 解释器)时,这正是语言开发人员正在做的事情。
SICP,第 3 章中显示了对环境的良好解释
其他的建议
AFAIK,来自 Emacs 23,ELisp 使用词法作用域和动态作用域(类似于 Common Lisp,见下文)。
Common Lisp 对局部变量使用词法作用域(由let
表单引入)和对全局变量使用动态作用域(它们也称为special;可以声明一个局部变量 special,但很少使用)用defvar
and定义defparameter
。为了将它们与词法范围的变量区分开来,它们的名称通常带有“耳罩”,例如*standard-input*
. 顶级函数在 CL 中也很特殊,这可能相当危险:人们可以通过隐藏顶级函数在不知不觉中改变行为。这就是为什么 CL 标准在标准库函数上指定锁以防止它们重新定义的原因。
相反,Scheme 总是使用词法作用域。然而,动态范围有时很有用(Richard Stallman 对此提出了很好的观点)。为了克服这个问题,许多 Scheme 实现引入了所谓的参数(使用词法作用域实现)。
Common Lisp、Scheme、Clojure、Python 等语言保持对变量的动态引用:您可以从字符串(在 Lisp 的术语中是一个符号)构造变量名并找到它的值。更多的静态语言,如 C、OCaml 或 Haskell,无法做到这一点(除非使用某种形式的反射)。但这与他们使用什么样的范围关系不大。