closureには何が保存されるのか
closureとは関数にデータを保存するための仕組みです。
Lisp大好きな人はlambda
と一緒に多用します。
例えばこんな使い方をします。
(let ((x 0)) (setq *call* (lambda () (incf x)))) (funcall *call*) -> 1 (funcall *call*) -> 2 (funcall *call*) -> 3 (funcall *call*) -> 4
上記の例では、lambda
により生成された無名関数が、
変数x
をclosureにて保存しています。
let
で宣言された変数x
のスコープはとっくに終わっているにもかかわらず、
funcall
で関数を呼び出すと、変数x
は立派に役目を果たしているというわけです。
では本題ですが、closureは何を対象にどんなものを保存するのでしょうか。
lexical変数だよ!と思った人は正解です。
問題はそれだけなのかということ。
典型的な回答としては下記の通り。
- lexical変数
flet
,labels
関数
flet
, labels
は局所関数を作成するためのものです。
変数ではなく関数です。
例えばこんな感じ。
(flet ((call () :hello)) (setq *call* (lambda () (call)))) (funcall *call*) -> :HELLO
面倒な話題として、setf
関数もclosureの対象となります。
説明すると長くなるので手短にしますが、
defsetf
とdefine-setf-expander
で定義されたマクロ形式の方ではなく、
flet
, labels
で定義した(setf 名前)
形式の関数がclosureの保存対象となります。
何が違うんだって思われるかもしれませんが、なんか結構違うんです。
そのうち詳しく説明します。
例をあげます。
(flet (((setf aaa) (value cons) (rplaca cons (* value value)) value)) (setq *call* (lambda (c v) (setf (aaa c) v)))) (let ((c (cons 10 20))) (funcall *call* c 999) c) → (998001 . 20)
ではdefun
宣言による関数はclosureに保存されないのでしょうか?
実はされません。
下記に例をあげます。
(defun aaa () :hello) (setq *call* (lambda () (aaa))) (funcall *call*) →:HELLO (defun aaa () :zzz) (funcall *call*) →:ZZZ
defun
による大域関数はspecial
変数みたいな扱いなんですね。
ここまではどのCommon Lispでも通用するでしょう。
ここからは処理系に依存する話になるかもしれません。
話は大きく変わり、tagbody
の実装をしたときの話です。
tagbody
のスコープを脱出したgo
について考えていました。
tagbody
は動的エクステントなので、スコープの外で呼び出されたgo
は無効となります。
問題は「無効」という意味が、放っておいてもいいオブジェクト
という意味では「ない」ということです。
無効になったtagは、呼び出された時点でエラーになります。
処理系は「エラーになる」という所まで面倒見てやらなければいけないのです。
例えば次の通り。
(tagbody (format t "Hello~%") again (format t "Tagbody~%") (setq *call* (lambda () (go again)))) (funcall *call*) →エラー、againはすでに閉じられている
どうすればいいのか。
go
に結びつけられたtagを専用のオブジェクトにするのです。
tagには戻り先のtagbody
に加えて、有効無効を表すフラグを持たせます。
tagはtagbody
とgo
で共有されます。
もしtagbody
がスコープを抜けたら、持っているすべてのtagに無効フラグを立たせます。
もしgo
が無効のtagを呼び出したらエラーにします。
では、そのtagオブジェクトは誰がいくつ保有するのか。
最初の考えだと、tagオブジェクトはtagbody
のコードが唯一保有すること考えていました。
つまり、(tagbody ...)
という表記が現れたら、
そのtagbody
がtagオブジェクトを保有するのだという考えです。
でもこれだと、再帰呼出か、スレッドによる同時実行により破綻します。
オブジェクトが一個しかないのですから。
解決方法は、tagオブジェクトはtagbody
を実行するたびに生成するしかなかったのです。
感覚としてはこんな感じ。
(let ((again (make-tagobject%))) (tagbody% (format t "Hello~%") again (format t "Tagbody~%") (setq *call* (lambda () (go% again))) (close-tagobject% again))
となると、当然tagオブジェクトもclosureに保存する必要が出てきます。
話が長くなりましたが、結局はtagbody
のtagもclosureに入れるんだよっていうことです。
全く同じ話がblock
/return-from
にも当てはまります。
しかしcatch
/throw
は対象外。
catch
はspecial
変数みたいな扱いなので必要ありません。
結論は、closureには下記のオブジェクトが格納される。
- lexical変数
flet
,labels
関数flet
,labels
のsetf
関数tagbody
のtagblock
のsymbol