13.11 Открытые замыкания

Традиционно функции являются непрозрачными объектами, которые не предлагают никакой функциональности, кроме как вызов. (Функции Emacs Lisp не являются полностью непрозрачными, поскольку из них можно извлечь некоторую информацию, такую как их строковый комментарий, список аргументов или интерактивная спецификация, но они все еще в основном непрозрачны.) Обычно это то, что нам нужно, но иногда нам необходимо, чтобы функции раскрывали немного больше информации о себе.

Открытые замыкания, или сокращенно OClosures, — это объекты функций, которые несут дополнительную информацию о типе и раскрывают некоторую информацию о себе в виде слотов, к которым можно получить доступ с помощью функций доступа.

OClosures определяются в два этапа: сначала вы используете 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
  "Макрос клавиатуры."
  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))))

Как вы можете видеть, слоты keys и counter OClosure могут быть доступны как локальные переменные изнутри тела OClosure. Но теперь мы также можем получить к ним доступ извне тела OClosure, например, для описания макроса клавиатуры:

(defun describe-kbd-macro (km)
  (if (not (eq 'kbd-macro (oclosure-type km)))
      (message "Не макрос клавиатуры")
    (let ((keys    (kbd-macro--keys km))
          (counter (kbd-macro--counter km)))
      (message "Keys=%S, вызвано %d раз" keys counter))))

Где kbd-macro--keys и kbd-macro--counter — это функции доступа, сгенерированные макросом oclosure-define для OClosures типа kbd-macro.

Макрос: oclosure-define oname &optional docstring &rest slots

Этот макрос определяет новый тип OClosure вместе с функциями доступа для его слотов. oname может быть символом (именем нового типа) или списком вида (oname . type-props), в котором type-props — это список дополнительных свойств этого типа oclosure. slots — это список описаний слотов, где каждый слот может быть либо символом (именем слота), либо иметь вид (slot-name . slot-props), где slot-props — это список свойств соответствующего slot-name. Свойства типа OClosure, указанные в type-props, могут включать следующее:

(:predicate pred-name)

Это запрашивает создание функции предиката с именем pred-name. Эта функция будет использоваться для распознавания OClosures типа oname. Если это свойство типа не указано, oclosure-define сгенерирует имя по умолчанию для предиката.

(:parent otype)

Это делает тип otype OClosures родителем типа oname. OClosures типа oname наследуют слоты, определенные их родительским типом.

(:copier copier-name copier-args)

Это вызывает определение функции обновления, называемой copier, которая принимает OClosure типа oname в качестве первого аргумента и возвращает его копию с измененными слотами, указанными в copier-args, чтобы они содержали значение, переданное в соответствующий аргумент при фактическом вызове copier-name.

Для каждого слота в slots макрос oclosure-define создает функцию доступа с именем oname--slot-name; их можно использовать для доступа к значениям слотов. Определения слотов в slots могут указывать следующие свойства слотов:

:mutable val

По умолчанию слоты неизменяемы, но если вы укажете свойство :mutable с ненулевым значением, слот может быть изменен, например, с помощью setf (см. Макрос setf).

:type val-type

Это указывает тип значений, ожидаемых в слоте.

Макрос: oclosure-lambda (type . slots) arglist &rest body

Этот макрос создает анонимный OClosure типа type, который должен был быть определен с помощью oclosure-define. slots должен быть списком элементов вида (slot-name expr). В время выполнения каждый expr вычисляется по порядку, после чего OClosure создается с инициализированными слотами соответствующими значениями.

При вызове в качестве функции (см. Вызов функций) OClosure, созданный этим макросом, будет принимать аргументы в соответствии с arglist и выполнит код в body. body может ссылаться на значение любого из его слотов напрямую, как если бы это была локальная переменная, захваченная статическим областью видимости.

Функция: oclosure-type object

Эта функция возвращает тип OClosure (символ) object, если это OClosure, и nil в противном случае.

Еще одна функция, связанная с OClosures, — это oclosure-interactive-form, которая позволяет некоторым типам OClosures динамически вычислять свои интерактивные формы. См. oclosure-interactive-form.