確かに: すべての部分式に対して、その部分式の自由変数を計算し、それを何らかの方法で AST にアタッチします。次に、 を再帰的に呼び出すたびにeval
、環境を評価しようとしている式の自由変数だけに制限します。
コンパイラは通常、lambda
境界でこれを行い、不要な値への参照を保持するクロージャーを作成しないようにします。これらの参照を保持すると、オブジェクトが GC されるのを防ぐ可能性があるためです。つまり、次のプログラムでは
(let ([x 1]
[y 2])
(lambda (z) ;; free vars = {x}
(+ x z))
式のクロージャにlambda
は の値が含まれますが、 は含まれx
ません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))))
理由は同じです。参照をできるだけ早く削除すると、オブジェクトをより早く解放できる可能性があるということです。(これは、閉鎖の場合と同様に、キャプチャされた継続によって保持されないことも意味します。) 詳細については、Google の「安全なスペース」を参照してください。