従来、関数とは呼び出し以外の機能を一切持たない不透明なオブジェクトである。(Emacs Lispの関数は、docstringやarglist、インタラクティブ仕様といった一部の情報を抽出できるため、完全に不透明というわけではないが、それでも概ね不透明である。)通常はこれで十分だが、時折、関数に自身に関する情報をもう少し開示させたい場合がある。
オープンクロージャ(略してOClosures)とは、追加の型情報を保持し、アクセサ関数経由でアクセス可能なスロットという形で自身に関する情報の一部を公開する関数オブジェクトのことです。
OClosureは2段階で定義されます。まずoclosure-defineを用いて、この型のOClosureが保持するスロットを指定することで新しいOClosure型を定義し、次にoclosure-lambdaを用いて指定した型のOClosureオブジェクトを作成します。
例えば、キーボードマクロ(つまり、一連のキーイベントを再実行するインタラクティブな関数。キーボードマクロを参照)を定義したいとしましょう。これは、次のような単純な関数で実現できます。
(defun kbd-macro (key-sequence)
(lambda (&optional arg)
(interactive "P")
(execute-kbd-macro key-sequence arg)))
しかし、このような定義では、その関数からkey-sequenceを取り出す簡単な方法がなく、例えばそれを出力するといったことができません。
OClosuresを用いて、次のようにこの問題を解決できます。まず、キーボードマクロの型を定義します(ついでに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の本体内からローカル変数としてアクセスできます。しかし、現在ではOClosureの本体外からもこれらにアクセスできるようになりました。例えば、キーボードマクロを記述する場合などです。
(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 マクロによって生成されるアクセサ関数である。
このマクロは、新しい OClosure 型と、その slots に対するアクセサ関数を定義します。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)これにより、OClosuresの型 otype が型 oname の親となります。型 oname の OClosures は、親型で定義された slots を継承します。
(:copier copier-name copier-args)これにより、copierとして知られる関数型更新関数が定義されます。この関数は、第1引数として型onameのOClosureを受け取り、copier-argsで指定されたスロット名を持つそのコピーを返します。その際、copier-nameの実際の呼び出しにおいて対応する引数で渡された値が含まれるよう、スロットが修正されています。
slots の各スロットに対して、oclosure-define マクロは oname--slot-name
という名前のアクセサ関数を生成します。これらを使用してスロットの値にアクセスできます。slots
のスロット定義では、スロットの以下のプロパティを指定できます。
:mutable valデフォルトではスロットは不変ですが、:mutable プロパティに nil 以外の値を指定すると、例えば setf
を用いてスロットを変更できます(setf
マクロ を参照)。
:type val-typeスロットに現れることが期待される値の型を指定します。
このマクロは、本来 oclosure-define で定義されるべきだった型 type の匿名 OClosure を生成します。slots は
(slot-name expr) 形式の要素のリストである必要があります。実行時には、各 expr が順に評価され、その後、得られた値でスロットが初期化された状態で OClosure が生成されます。
関数として呼び出された場合(関数の呼び出しを参照)、このマクロによって生成された OClosure は arglist に従って引数を受け取り、body 内のコードを実行します。body は、あたかも静的スコープによってキャプチャされたローカル変数であるかのように、自身の任意のスロットの値を直接参照できます。
この関数は、object が OClosure であればその OClosure
型(シンボル)を、そうでなければ nil を返します。
OClosuresに関連するもう一つの機能にoclosure-interactive-formがあり、これにより一部の種類のOClosuresがインタラクティブ形式を動的に計算できるようになります。oclosure-interactive-formを参照してください。