13.11 Open Closures

Traditionally, functions are opaque objects which offer no other functionality but to call them. (Emacs Lisp functions aren’t fully opaque since you can extract some info out of them such as their docstring, their arglist, or their interactive spec, but they are still mostly opaque.) This is usually what we want, but occasionally we need functions to expose a bit more information about themselves.

Open closures, or OClosures for short, are function objects which carry additional type information and expose some information about themselves in the form of slots which you can access via accessor functions.

OClosures are defined in two steps: first you use oclosure-define to define a new OClosure type by specifying the slots carried by the OClosures of this type, and then you use oclosure-lambda to create an OClosure object of a given type.

Let’s say we want to define keyboard macros, i.e. interactive functions which re-execute a sequence of key events (see Keyboard Macros). You could do it with a plain function as follows:

(defun kbd-macro (key-sequence)
  (lambda (&optional arg)
    (interactive "P")
    (execute-kbd-macro key-sequence arg)))

But with such a definition there is no easy way to extract the key-sequence from that function, for example to print it.

We can solve this problem using OClosures as follows. First we define the type of our keyboard macros (to which we decided to add a counter slot while at it):

(oclosure-define kbd-macro
  "Keyboard macro."
  keys (counter :mutable t))

After which we can rewrite our kbd-macro function:

(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))))

As you can see, the keys and counter slots of the OClosure can be accessed as local variables from within the body of the OClosure. But we can now also access them from outside of the body of the OClosure, for example to describe a keyboard macro:

(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))))

Where kbd-macro--keys and kbd-macro--counter are accessor functions generated by the oclosure-define macro for oclosures whose type is kbd-macro.

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

This macro defines a new OClosure type along with accessor functions for its slots. oname can be a symbol (the name of the new type), or a list of the form (oname . type-props), in which case type-props is a list of additional properties of this oclosure type. slots is a list of slot descriptions where each slot can be either a symbol (the name of the slot) or it can be of the form (slot-name . slot-props), where slot-props is a property list of the corresponding slot slot-name. The OClosure type’s properties specified by type-props can include the following:

(:predicate pred-name)

This requests creation of a predicate function named pred-name. This function will be used to recognize OClosures of the type oname. If this type property is not specified, oclosure-define will generate a default name for the predicate.

(:parent otype)

This makes type otype of OClosures be the parent of the type oname. The OClosures of type oname inherit the slots defined by their parent type.

(:copier copier-name copier-args)

This causes the definition of a functional update function, knows as the copier, which takes an OClosure of type oname as its first argument and returns a copy of it with the slots named in copier-args modified to contain the value passed in the corresponding argument in the actual call to copier-name.

For each slot in slots, the oclosure-define macro creates an accessor function named oname--slot-name; these can be used to access the values of the slots. The slot definitions in slots can specify the following properties of the slots:

:mutable val

By default, slots are immutable, but if you specify the :mutable property with a non-nil value, the slot can be mutated, for example with setf (see The setf Macro).

:type val-type

This specifies the type of the values expected to appear in the slot.

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

This macro creates an anonymous OClosure of type type, which should have been defined with oclosure-define. slots should be a list of elements of the form (slot-name expr). At run time, each expr is evaluated, in order, after which the OClosure is created with its slots initialized with the resulting values.

When called as a function (see Calling Functions), the OClosure created by this macro will accept arguments according to arglist and will execute the code in body. body can refer to the value of any of its slot directly as if it were a local variable that had been captured by static scoping.

Function: oclosure-type object

This function returns the OClosure type (a symbol) of object if it is an OClosure, and nil otherwise.

One other function related to OClosures is oclosure-interactive-form, which allows some types of OClosures to compute their interactive forms dynamically. See oclosure-interactive-form.