词法作用域与动态作用域:OCaml表达式求值及非函数场景差异问询
1. 目标表达式的求值结果
对于表达式 let x = 5 in (let x = 6 in x),词法作用域和动态作用域下的结果完全一致,都是6。
道理很直白:内层的let x = 6 in x在自己的块里创建了全新的x绑定,直接用这个绑定的值,和外层的x=5没任何关系。两种作用域的核心差异是“找未在当前块定义的变量”时的规则——词法找定义时的环境,动态找调用时的环境,但这个表达式里不存在这种跨块的变量引用,所以结果没区别。
2. 除函数应用/闭包外的差异场景
除了函数闭包、函数应用这类经典场景,以下情况也会让两种作用域下的求值结果不同:
动态求值(Eval)场景
比如在支持动态求值的语言(如Scheme)中,用eval执行引用外部变量的代码:(define x 5) (let ((x 6)) (eval 'x))词法作用域下,
eval会引用顶层的x=5;动态作用域下则会用当前块的x=6,结果一5一6,完全不同。特殊动态绑定变量场景
以Common Lisp的特殊变量为例,这类变量的查找遵循动态作用域,哪怕没有闭包捕获:(defvar *x* 5) (defun print-x () (print *x*)) (let ((*x* 6)) (print-x))这里
print-x没有词法捕获*x*,但因为*x*是特殊变量,动态作用域下执行print-x会输出6;如果是普通词法变量的话,会输出定义时的5。宏展开场景
宏是在代码展开阶段执行的,部分宏的行为会依赖动态环境而非词法环境:
比如Common Lisp中这个例子:(defvar *macro-var* 5) (defmacro get-macro-var () `',*macro-var*) (let ((*macro-var* 6)) (get-macro-var))词法作用域下,宏展开时会取定义时的
*macro-var*=5;动态作用域规则下,宏展开会使用当前环境的6,最终结果不同。异常上下文的变量引用
部分语言的异常处理机制中,异常处理块引用的未绑定变量会遵循动态作用域:比如异常抛出时的环境里有某个变量绑定,词法作用域会找处理块定义时的绑定,动态作用域则用抛出时的绑定,结果自然不同。
内容的提问来源于stack exchange,提问作者penguin365

