CLパッケージは、Common Lispのletフォームにより厳密に従う
下記のマクロを定義する:
このフォームは、確立する束縛が純粋にレキシカルである点以外はlet
とほぼ似ている。レキシカルな束縛はCのような言語の局所変数と
類似している: 物理的にlexical-let(マクロ展開後)の本体内の
コードだけが束縛変数を参照してよい。
(setq a 5)
(defun foo (b) (+ a b))
(let ((a 2)) (foo a))
⇒ 4
(lexical-let ((a 2)) (foo a))
⇒ 7
この例では、aの正規のlet束縛は実際グローバル変数a
を一時的に変更するので、fooは2へのaの束縛を
見ることができる。しかしlexical-letは実際本体内で使うために別の
局所変数aを作り、同名のグローバル変数にどんな影響もない。
レキシカルな束縛の最も重要な使用法はクロージャを作ることである。 クロージャは外のレキシカル変数を参照する関数オブジェクトである。 たとえば:
(defun make-adder (n)
(lexical-let ((n n))
(function (lambda (m) (+ n m)))))
(setq add17 (make-adder 17))
(funcall add17 4)
⇒ 21
呼び出し(make-adder 17)は引数に17を加える関数オブジェクトを
戻す。letがlexical-letの代わりに使われる場合、
関数オブジェクトはmake-adder自身の呼び出しの間だけ17へ
束縛されているグローバルのnを参照するだろう。
(defun make-counter ()
(lexical-let ((n 0))
(function* (lambda (&optional (m 1)) (incf n m)))))
(setq count-1 (make-counter))
(funcall count-1 3)
⇒ 3
(funcall count-1 14)
⇒ 17
(setq count-2 (make-counter))
(funcall count-2 5)
⇒ 5
(funcall count-1 2)
⇒ 19
(funcall count-2)
⇒ 6
ここでmake-counterへの各呼び出しは別の局所変数nを作る。
それは戻される関数オブジェクト用の私的なカウンターとして働く。
その上で閉じられたレキシカル変数は、ちょうど他のLispオブジェクトと
同様にそれへの最後の参照がなくなるまで存続する。たとえば、
count-2は変数nのインスタンスを参照する関数オブジェクトを
参照する; これはその変数への唯一の参照なので、
(setq count-2 nil)の後にはガベジコレクタはnのこの
インスタンスを削除できるだろう。もちろん、lexical-letが実際には
クロージャをまったく作らない場合、レキシカル変数はlexical-letが
戻るとすぐに自由である。
多くのクロージャはそれらが参照する束縛のエクステントの間だけ使われる;
これらはLisp語法では“downward funargs”として知られている。クロージャ
がこのように使われる場合、正規のEmacs Lisp動的束縛で十分であり、
lexical-letクロージャより効率的だろう。
(defun add-to-list (x list)
(mapcar (function (lambda (y) (+ x y))) list))
(add-to-list 7 '(1 2 5))
⇒ (8 9 12)
このラムダはxがまだ束縛されているときにのみ使われているので、
そのために真のクロージャを作ることは必要ではない。
名前付きクロージャを作るためにlexical-letの内部でdefunや
fletを使うことができる。いくつかのクロージャが1個の
lexical-letの本体で作られる場合、それらはすべてレキシカル変数の
同じインスタンスの上で閉じている。
lexical-letフォームはCommon Lispへの拡張である。真のCommon Lisp
では、すべての束縛はそうではないと宣言されない限りレキシカルである。
このフォームはちょうどlexical-letと似ているが、束縛は
let*の方法のように順次に作られる点が異なる。