Next: , Previous: , Up: 関数   [Contents][Index]


13.11 オープンクロージャ

関数とは伝統的には呼び出し以外の機能を提供しない不透明なオブジェクトです。(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のスロットkeyscounterには、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--keyskbd-macro--counterはタイプがkbd-macroであるようなoclosureのために、oclosure-defineマクロによって生成されたアクセッサ関数です。

Macro: oclosure-define oname &optional docstring &rest slots

このマクロは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

そのスロットに期待される値のタイプを指定する。

Macro: oclosure-lambda (type . slots) arglist &rest body

このマクロはタイプtypeの無名OClosureを作成する。typeoclosure-defineによって定義されていること。slots(slot-name expr)という形式の要素からなるリスト。実行時にはexprが順に評価された後に、その結果の値によってスロットが初期化されたOClosureが作成される。

関数として呼び出された場合には(関数の呼び出しを参照)、このマクロによって作成されたOClosureはarglistに応じた引数を受け取り、bodyのコードを実行する。bodyからはあたかも静的スコープにキャプチャされたローカル変数のように任意のスロットの値を直接参照できる。

Function: oclosure-type object

この関数はobjectがOClosureであればそのOClosureタイプ(シンボル)そうでなければnilをリターンする。

他にもOClosureに関連する関数としてoclosure-interactive-formがあります。これは一部のOClosureタイプにたいしてダイナミックなinteractiveフォームの算出を可能にする関数です。oclosure-interactive-formを参照してください。