関数とは伝統的には呼び出し以外の機能を提供しない不透明なオブジェクトです。(Emacs Lisp関数はdoc文字列や引数リスト、interactive仕様のようないくつかの情報を抽出できるので完全に不透明という訳ではないが、そのほとんどは依然として不透明である)。これはわたしたちの要望を通常は満たしていますが、関数自体に関する情報をもう少し公開する必要がある場合もあるでしょう。
オープンクロージャ(Open closure)、略してOClosureは追加のタイプ情報をもった関数オブジェクトであり、アクセッサ関数を通じてアクセス可能なスロット形式で自身の情報の一部を公開します。
OClosureは2つのステップから定義されます。まずoclosure-define
を使ってそのタイプのOClosuresがもつスロットを指定することにより新たなOClosureタイプを定義します。それからoclosure-lambda
を使用して与えられたタイプのOClosureオブジェクトを作成するのです。
たとえばキーボードマクロ、すなわちキーイベントのシーケンスを再実行するインタラクティブな関数を定義したいとします(キーボードマクロを参照)。これは以下のような普通の関数で行うことができます:
(defun kbd-macro (key-sequence) (lambda (&optional arg) (interactive "P") (execute-kbd-macro key-sequence arg)))
しかしこのような定義では関数からkey-sequenceを抽出して、たとえばプリントするといったようなことを簡単に行う方法はありません。
この問題は以下のようなOClosureを使えば解決できます。まずはキーボードマクロのタイプを定義します(ついでにcounter
スロットを追加することにします):
(oclosure-define kbd-macro "Keyboard macro." keys (counter :mutable t))
その後にkbd-macro
関数の定義を書き換えることができます:
(defun kbd-macro (key-sequence) (oclosure-lambda (kbd-macro (keys key-sequence) (counter 0)) (&optional arg) (interactive "P") (execute-kbd-macro keys arg) (setq counter (1+ counter))))
ご覧のとおり、このOClosureのスロットkeys
とcounter
には、OClosureのbodyからローカル変数としてアクセスすることができます。しかしOClosureのbody外部からもアクセスできるようになったのです。たとえばキーボードマクロをdescribeする関数は:
(defun describe-kbd-macro (km) (if (not (eq 'kbd-macro (oclosure-type km))) (message "Not a keyboard macro") (let ((keys (kbd-macro--keys km)) (counter (kbd-macro--counter km))) (message "Keys=%S, called %d times" keys counter))))
ここでkbd-macro--keys
とkbd-macro--counter
はタイプがkbd-macro
であるようなoclosureのために、oclosure-define
マクロによって生成されたアクセッサ関数です。
このマクロはslotsのアクセッサ関数とともに、新たなOClosureタイプを定義する。onameはシンボル(新たなタイプの名前)、または(oname . type-props)
という形式のリスト。この場合のtype-propsはこのoclosureタイプの追加プロパティのリストである。slotsはスロットを記述するリストであり、スロットはそれぞれシンボル(スロットの名前)、または(slot-name . slot-props)
という形式を指定できる。ここでslot-propsは対応するスロットslot-nameのプロパティリストである。type-propsで指定するOClosureタイプのプロパティには以下を含めることができる:
(:predicate pred-name)
pred-nameという名前の述語関数の作成をリクエストする。その関数はタイプonameのOClosureの識別に使用される。このタイプのプロパティが未指定なら、oclosure-define
はその述語にたいするデフォルト名を生成する。
(:parent otype)
タイプotypeのOClosureをタイプonameの親にする。タイプonameのOClosureは親タイプで定義されたslotsを継承する。
(:copier copier-name copier-args)
copierと呼ばれる機能更新関数を定義する。1つ目の引数にタイプonameのOClosureを受け取り、copier-argsという名前のスロット(copier-nameの実際の呼び出し時には対応するはそれぞれ引数に渡された値が含まれるように変更される)をもつコピーをリターンする。
oclosure-define
マクロがslots内のスロットそれぞれにたいして、oname--slot-name
という名前で、スロットの値へのアクセスに使用できるアクセッサ関数を定義する。slotsでのそれぞれのスロットの定義では、以下のスロットのプロパティを指定できる:
:mutable val
デフォルトではスロットはimmutable(変更不可、不変)だが、:mutable
プロパティに非nil
を指定すれば、たとえばsetf
(setf
マクロを参照)でスロットを変更できるようにmutable(変更可能)になる。
:type val-type
そのスロットに期待される値のタイプを指定する。
このマクロはタイプtypeの無名OClosureを作成する。typeはoclosure-define
によって定義されていること。slotsは(slot-name expr)
という形式の要素からなるリスト。実行時にはexprが順に評価された後に、その結果の値によってスロットが初期化されたOClosureが作成される。
関数として呼び出された場合には(関数の呼び出しを参照)、このマクロによって作成されたOClosureはarglistに応じた引数を受け取り、bodyのコードを実行する。bodyからはあたかも静的スコープにキャプチャされたローカル変数のように任意のスロットの値を直接参照できる。
この関数はobjectがOClosureであればそのOClosureタイプ(シンボル)そうでなければnil
をリターンする。
他にもOClosureに関連する関数としてoclosure-interactive-form
があります。これは一部のOClosureタイプにたいしてダイナミックなinteractiveフォームの算出を可能にする関数です。oclosure-interactive-formを参照してください。