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
.
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.
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.
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.