Attach-stobj
Attach an “implementation stobj” to an attachable
stobj
For an illustration of attach-stobj, see community-books directory books/demos/attach-stobj/, in particular file
README.txt in that directory.
This topic assumes familiarity with abstract stobjs; see defabsstobj. It documents a way to modify the foundation and primitives of
an abstract stobj, gen, that is introduced by defabsstobj
using the keyword argument :attachable t. Such a stobj is called an
attachable stobj. Execution of its primitives can be provided by
corresponding primitives of a specified abstract stobj, impl, which we
say is attached to gen (or: impl is the implementation
stobj attached to gen); said differently, gen has impl as
an attachment. That relationship is specified by the following
General Form:
(attach-stobj gen impl)
where gen and impl are symbols, gen is not currently the
name of any event (function, macro, constant, stobj, etc.), and
impl is an abstract stobj. A subsequent attempt to introduce gen as
an attachable stobj will require gen and impl to have
corresponding logical skeletons as described below. In that case, the
foundation of gen, as well as execution of the primitives of gen,
will effectively be provided by impl; details are below.
In the General Form above, impl is allowed to be nil, i.e., the
event (attach-stobj gen nil) is legal, where it is still required that
gen not be the name of any existing event. The effect of this
``attachment'' of nil is to cancel the effect of any previous
(attach-stobj gen impl) on any future introduction of gen. Below,
we assume the common case that impl is not nil.
Note that impl may itself have an attachment, say, impl2, in
which case we say that impl2 is attached to gen. If furthermore
impl3 is attached to impl2, then impl3 is said to be attached
to gen; and so on.
The guiding principle is that gen is modified by its attachment to
impl so that gen behaves exactly as impl except for the names
introduced and the :non-executable keyword. Specifically, if impl
is attached to gen, then the following properties hold. (See defabsstobj for the notion of a “function spec” and its
“completion”.)
- Both gen and impl are abstract stobjs, and moreover, gen is
an attachable stobj.
- Impl was introduced before the use of attach-stobj to attach
impl to gen, which took place before gen was introduced.
- Gen and impl have corresponding logical skeletons (as defined
below). In particular, each primitive of gen has the same logical
meaning (i.e., the same :logic function) as the corresponding primitive
of impl.
- For each primitive p_gen of gen and corresponding primitive
p_impl of impl, if (p_impl . kwd-alist) is the completion of
the corresponding function spec for p_impl, then the function spec for
p_gen is effectively given as (p_gen . kwd-alist), with the
following exception. Each :updater keyword is replaced appropriately, as
follows: if kwd-alist specifies :updater u_impl and primitive
u_gen of gen corresponds to primitive u_impl of impl, then
:updater u_impl is replaced by :updater u_gen to obtain the
effective function spec for p_gen.
- The :protect-default and :congruent-to keyword arguments for
gen are effectively those of impl.
- The :non-executable keyword argument for gen is unchanged by the
attachment.
As promised above, we now define when two defabsstobj events have
corresponding logical skeletons, as follows. The :exports keyword
argument of each must be of the same length, establishing a positional one-one
correspondence between them. Corresponding exports (i.e., exports in the same
position) must have the same :logic functions and, if one of them
specifies :updater u1, then the other must specify :updater u2 where
u1 and u2 correspond.
Remarks on Performance. Stobj attachments are designed to be efficient.
There is no indirection: in raw Lisp, each primitive macroexpands to a call of
the :exec primitive of the attachment (which is itself a macro call).
The trade-off is that when a function F is defined in the course of
evaluating an include-book event, then if the guard or body of F
calls an attachable stobj primitive — either directly or by way of
inline functions or macroexpansion, and whether or not that stobj has an
attachment — then F will be compiled at that time. (This is in
contrast to the usual case, where compiled code from the book's compiled file
will be installed to avoid such recompilation.) Most likely this will not be
a noticeable problem in practice, but if it is, then one can define a wrapper
— a function that does nothing more than call the primitive — to
avoid such recompilation at the cost of a runtime function call for each such
primitive. End of Remarks on Performance.
Note that the notion of redundancy for defabsstobj was not changed by
support for the :attachable keyword. As noted in the topic redundant-events, a defabsstobj event is redundant if there is already
an identical such event in the logical world.
The community-books directory system/tests/attachable-stobjs/
has examples that use attachable stobjs.