[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
- To: Common-Lisp%su-ai@usc-ecl
- Subject: DEFSETF
- From: David A. Moon <Moon%SCRC-TENEX@MIT-ML>
- Date: Wed, 20 Apr 1983 07:58:00 -0000
- Mail-from: ARPANET site SU-AI rcvd at 20-Apr-83 1018-PST
The DEFSETF documented in the Laser edition is deficient in several
respects. The UNCONS example (about as obscure as one can get!) fails to
agree with the English. The manual is inconsistent about who is
responsible for ensuring proper order of evaluation. It doesn't seem to be
possible to write the DEFSETF for LDB; ignoring order of evaluation and
returned-value issues, (SETF (LDB x y) z) turns into (SETF y (DPB z x y));
note how the "y" argument is used as both an "Lvalue" and an "Rvalue".
This DEFSETF similarly gives no help in implementing such macros as INCF,
SHIFTF, and PUSH, which take a place that is both read and written. The
function generated by DEFSETF doesn't have a portable name, so the user may
not know how to trace and otherwise debug it. (This is best attributed to
the omission of function specs from Common Lisp, so I won't mention it
I would like to propose a modified form of DEFSETF:
There are three forms of DEFSETF, rather than the two in the Laser
manual. This helps a lot.
The body of DEFSETF is in charge of making sure that the right value is
returned. In most cases, this works simply by calling a storing
function that is guaranteed to return the right value. The SETF system
is in charge of evaluating the evaluatable subforms of the SETF (or
other) special form in left-to-right order, and once each (no more
and no less), or generating code whose effect is equivalent to that.
It is aided in this by some information supplied by the body of the
The underlying theory is that the SETF method for a given form
can be expressed as five values:
- a list of temporary variables
- a list of values to bind them to (subforms of the given form)
- a second list of temporary variables ("store variables")
- a storing form
- an accessing form
The store variables are to be bound to the values of the form to be stored.
In almost all cases we store single values and there is only one store
The storing form and the accessing form can be evaluated any number of
times, inside the bindings of the temporary variables (and of the store
variables, in the case of the storing form). The accessing form returns
the value of the generalized variable. The storing form modifies the
value of the generalized variable, and guarantees to return the store
variable(s) as its value(s); these are the correct values for SETF,
INCF, etc. to return. The value returned by the accessing form is
(of course) affected by execution of the storing form.
The temporary variables and the store variables are gensyms in all cases,
so that there is never any issue of name clashes with them. This is
necessary to make the special forms that do multiple setfs in parallel work
properly; these are PSETF, SHIFTF, and ROTATEF. Computation of the SETF
method must always cons new gensyms.
To give some examples:
- for a variable X
() () (G0001) (SETQ X G0001) X
- for (CAR exp)
(G0001) (exp) (G0002) (RPLACA2 G0001 G0002) (CAR G0001)
where RPLACA2 is assumed to be a version of RPLACA that returns
its second argument.
- for (LIST a b)
() () (G0001) (PROGN (SETQ a (FIRST G0001))
(SETQ b (SECOND G0001))
(COMPILE-TIME-ERROR "LIST-destructuring SETF is write-only")
Destructuring patterns are write-only (it doesn't seem worth
putting in a third set of temporary variables to make this work!)
I guess &OPTIONAL and &REST should be allowed in the store variables, if
we want to keep the features involving them that are in the Laser edition
DEFSETF. I don't currently support this.
SETF methods are defined via DEFSETF. There are three forms of DEFSETF.
- the trivial form (DEFSETF access-function storing-function [documentation])
The storing-function takes the same arguments as the access-function,
plus one additional argument, which is the value to store, and returns
that as its value.
Example: (DEFSETF SYMBOL-VALUE SET)
- the functional form (DEFSETF access-function (args...) (store-variables...)
The access-function is truly a function and evaluates its arguments.
Furthermore a SETF reference evaluates all the arguments (i.e. this is not
a "destructuring" case). Args are the lambda-list for the
access-function; &OPTIONAL and &REST are permitted. Optional args may
have defaults, although many cases that need them may prefer to use the
general form (below). The SETF method consists of some gensym temporary
variables, some gensym store variables, (access-function temporaries...)
as the accessing form, and the result of evaluating the body as the
storing form. During the evaluation of the body, the variables in args
and in store-variables will be bound to the corresponding gensyms. The
gensyms will in turn be bound to the arguments to the access function, and
the values being stored, at run time.
Example: (DEFSETF SUBSEQ (SEQUENCE START &OPTIONAL END) (NEW-SEQUENCE)
(REPLACE ,SEQUENCE ,NEW-SEQUENCE :START1 ,START :END1 ,END)
- the general form (DEFINE-SETF-METHOD access-function (subforms...)
The result of evaluating the body must be five values, as documented
above. The subforms variables are bound to a destructuring of the cdr of
the reference, just as in DEFMACRO. Note that the general case differs
from the functional case in that the general case's subforms pattern is
bound to parts of the reference form, whereas the functional case's args
pattern is bound to gensyms.
I gave this a separate name rather than kludging up DEFSETF's syntax
somehow with an extra level of parentheses or something. It's reasonable
for this to have a longer name since it is more low-level.
Example: (Sorry, there are by definition no small examples of this.
Pay no attention to the non-Common-Lisp functions herein.)
(DEFINE-SETF-METHOD CONS (CAR CDR)
(MULTIPLE-VALUE-BIND (VARS1 VALS1 STORES1 STORE-FORM1)
(MULTIPLE-VALUE-BIND (VARS2 VALS2 STORES2 STORE-FORM2)
(LET ((CONS-VAR (GENSYM)))
(VALUES (APPEND VARS1 VARS2)
(APPEND VALS1 VALS2)
`(PROGN ,(LET-SUBST STORES1 `((CAR ,CONS-VAR)) STORE-FORM1)
,(LET-SUBST STORES2 `((CDR ,CONS-VAR)) STORE-FORM2)
"CONS-destructuring SETF is write-only"))))))
A random, but useful, abbreviation for DEFSETF is
DEFINE-SETF-EQUIVALENCE access-function outer-function inner-function
This is a kind of macro that is only expanded in references (not forms).
SETF, LOCF, and their variants know about this. [The existence of
LOCF in the Lisp machine makes this extra useful.]
Example: (DEFINE-SETF-EQUIVALENCE CDADDR CDR CADDR)
Another thing that is random but useful is
DEFINE-MODIFY-MACRO name (args) function [documentation]
where args may contain &OPTIONAL and &REST, and the expansion
is like the following except that it has correct semantics:
(DEFMACRO name (location args...)
`(SETF ,location (function ,location ,args...)))
Example: (DEFINE-MODIFY-MACRO INCF (&OPTIONAL (DELTA 1)) +)
I anticipate that users will want to define many more of these
in addition to the mere two that Common Lisp has built in.
It would of course be a loss if it was too hard for users to
define modify macros that follow proper order of evaluation
semantics. I have needed the following, for example:
(DEFINE-MODIFY-MACRO UNIONF (OTHER-SET) UNION)
Thanks to Alan Bawden for explaining the underlying theory of the five
values to me.
By the way, this is all implemented and works. The code is optimized,
removing any temporary-variable bindings that are not actually necessary,
using code-analysis tools that I intend to offer as a yellow-pages
package once I have rewritten them to be portable. Some implementations
may have smart enough compilers that they don't need this, possibly.
The new LOOP will depend on some of these tools also.