closed-let

letで束縛された変数は外からは見えないが、関数を介してアクセスすることが出来る。定義されたときの変数を参照するのがレキシカルクロージャの特徴である。

(defun f1 ()
  (let ((foo 100))
    (values
     (lambda (n) (+ foo n))
     (* foo foo))))

(f1)
|
#<lexical-closure: (anonymous)>
10000

(funcall (setq x (f1)) 100)
|
200

この変数がスペシャル変数になると、もはや外からはアクセスできなくなり、グローバルに定義された値か、実行された環境での値を取る。ダイナミックな動作をする。

(defvar foo 0)
|
foo

(f1)
#<lexical-closure: (anonymous)>
10000

(funcall x 100)
|
100

(let ((foo -100))
  (f1))
|
#<lexical-closure: (anonymous)>
10000

(let ((foo -100))
  (funcall x 100))
|
0

一度スペシャル変数にした変数は、環境をリセットでもしないと元に戻らない。レキシカルクロージャ局所変数を外で用いる使い方をしていた場合、その変数がスペシャル変数になってしまうと動作に支障をきたす。もし、システムの重要な位置を占めていたら再起動しなくてはならない。


そんなわけで、局所変数スペシャル変数にならないように色々な工夫がされる。*hoge*のような、*で括った変数しかスペシャル変数にしない。普段いじらないようなモジュールに分ける。こういった方法は変数の使い勝手をそのままに、スペシャル変数に設定されるのを避ける良い方法である。とはいえ、故意に設定することはできるため、信頼性に欠ける。


もう一つの方法は、変数名を外から見えないようにするやり方である。変数名を隠蔽すれば、その変数をスペシャル変数に設定することはできないわけだ。(gensym)で作られたシンボルはインターンされないため、たとえ同じ名前の変数をスペシャル変数にしても、クロージャに影響を与えることはない。

(defmacro clet-1 (let-type let-form &rest args)
  (let ((defs (mapcar (lambda (x) `(,(car x) ,(gensym))) let-form))
        (f (lambda (x y) (subst (cadr y) (car y) x))))
    `(,let-type ,(reduce f defs :initial-value let-form)
       ,@(reduce f defs :initial-value args))))

(defmacro def-clet (let-type)
  `(defmacro ,(intern (concat "c" (symbol-name let-type))) (let-form &rest args)
     `(clet-1 ,',let-type ,let-form ,@args)))

(series 1 def-clet
  let let*)

(defun f2 ()
  (clet ((foo 100))
    (lambda () foo)))

(setq y (f2))
|
#<lexical-closure: (anonymous)>

(funcall y)
|
100

clet・・・closed-letを使って束縛される変数は、(gensym)の変数に置き換えられる。置き換えにはオブジェクト名をそのまま使うため、このあとで外から同じ名前の変数を入れても同じ変数にならない。これが問題になるのは、別のマクロと組み合わせたときである。

(defmacro m1 ()
  `(lambda () bar))

(defun f3 ()
  (let ((bar 2))
    (m1)))

(funcall (f3))
|
2

(defun f4 ()
  (clet ((bar 2))
    (m1)))

(funcall(f4))
|
変数が定義されていません: bar

(defun f5 ()
  (clet ((bar 3))
    (macrolet ((m2 ()
                 `(lambda () bar)))
      (m2))))

(funcall (f5))
|
3

外からその変数を注入しない場合や、cletの内側に変数を送り込むようなマクロであれば問題はないので、これは信頼性とのトレードオフだろう。

ところが・・・

;xyzzy
(si:closure-variable y)
|
((#:G21723 . 100))

(macrolet ((h () (let ((x (caar (si:closure-variable y))))
                   `(defvar ,x 'hoge))))
  (h))
|
#:G21723

(funcall y)
|
hoge

xyzzyでは容易くアクセスできてしまう。そんなわけで、xyzzyの主要な部分ではクロージャのこうした使い方はされていないのである。