当然:对于每个子表达式,计算该子表达式的自由变量并以某种方式将其附加到 AST。然后在每次递归调用 时eval
,将环境限制为您将要评估的表达式的自由变量。
编译器通常在lambda
边界处这样做以避免创建保留对不必要值的引用的闭包,因为保留这些引用可能会阻止对象被 GC'd。也就是说,对于下面的程序
(let ([x 1]
[y 2])
(lambda (z) ;; free vars = {x}
(+ x z))
表达式的闭包lambda
将包含x
但 not的值y
。但总的来说,这样做意味着您不能使用非常简单的“帧列表”环境表示;您可能必须将其展平(或至少复制和修剪)。
一些实现也会在局部变量不再使用时(尤其是在非尾调用之前)将局部变量(不被闭包保存的变量,您希望在寄存器或堆栈中看到的那种)清零。那是,
(let ([x some-big-object])
(f (g x) long-running-expression-not-involving-x))
可能会被翻译成低级的等价物
(let ([x some-big-object])
(let ([tmp1 (g x)])
(set! x #f)
(let ([tmp2 long-running-expression-not-involving-x])
(f tmp1 tmp2))))
原因是一样的:尽早删除引用意味着可以更快地释放对象。(这也意味着它们不会被捕获的延续所束缚,类似于关闭案例。)谷歌“空间安全”以获取更多信息。