[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
*** POLL *** Issue: SETF-FUNCTION-VS-MACRO (version 6)
- To: CL-CLEANUP@Sail.Stanford.EDU
- Subject: *** POLL *** Issue: SETF-FUNCTION-VS-MACRO (version 6)
- From: masinter.pa@Xerox.COM
- Date: 30 Nov 88 10:33 PST
- Cc: masinter.pa@Xerox.COM
- Line-fold: NO
- Reply-to: cl-cleanup@sail.stanford.edu
JonL has objected to the merger of the two writeups on the grounds that:
a) precedent: we've rarely reported an issue with two proposals
b) partiality: I've not presented the arguments as strongly as the
proponents of either "side" might; my advocacy is "misguided".
c) length:the merged version is as long as the sum of the sizes
of the individual writeups.
I have discovered, to my embarassement, that I neglected to delete
from "NAMED-BY-LIST" a fairly large section that was covered in
"COMMON-PART".
I would like to get a sense of the rest of the committee: would you please
respond to this message on the following question:
***** Should we continue to attempt to arrive at one writeup with two
separate proposals? *******
Thanks,
Larry
!
Issue: SETF-FUNCTION-VS-MACRO
References: SETF rules for what -place- can be (pp.94-7)
COMPILE function (p.438)
DEFUN macro (p.57)
DISASSEMBLE function (p.439)
DOCUMENTATION function (p.440)
FBOUNDP function (p.90)
FLET special form (p.113)
FMAKUNBOUND function (p.92)
FTYPE declaration (p.158)
FUNCTION special form (p.87)
FUNCTION declaration (p.159)
INLINE declaration (p.159)
NOTINLINE declaration (p.159)
LABELS special form (p.113)
SYMBOL-FUNCTION and setf of symbol-function (p.90)
TRACE macro (p.440)
UNTRACE macro (p.440)
X3J13 88-002R:
Accessing Slots, Ch. 1 p.11
DEFGENERIC Ch. 2 pp.26-9
DEFMETHOD Ch. 2 pp.39-41
(SETF DOCUMENTATION) Ch. 2 pp.43-5
ENSURE-GENERIC-FUNCTION Ch. 2 pp.46-7
GENERIC-FLET Ch. 2 p.52
GENERIC-LABELS Ch. 2 p.55-6
WITH-ADDED-METHODS Ch. 2 pp.90-1
Category: ADDITION
Edit history: Version 1, 13-Oct-87 Moon
(based on discussion among the CLOS working group)
Version 2, 26-Oct-87 Masinter, minor mods
Version 3, 4-Nov-87 Moon, small clarifications at KMP's urging
Version 4, 15-Nov-88 JonL, complete rewrite
Version 5, 26-Nov-88 Masinter, merge Versions 3 & 4.
Version 6, 30-Nov-88 Masinter, shorten
PROBLEM DESCRIPTION:
The SETF mechanism in Common Lisp was designed to allow for a
uniform way of refering to the "update" function of an accessor without
having to have separate names for the updator. The SETF macro shields the user
from ever having to know such names; the correlation between an accessor
name and its corresponding updator name, or updating code sequence, was
established by DEFSETF and DEFINE-SETF-METHOD. Update function names
like SET and RPLACA were retained primarily for backward compatibility.
However, in CLOS programming, generic updator functions must
be specified in pieces, by incremental uses of DEFMETHOD. For this,
and a variety of other reasons, the CLOS specification 88-002R
(accepted by X3J13) assumed that lists of the form (SETF <name>) were
acceptable to a wide variety of functions and macros as a way to
specify the "name" of the SETF function. The problem is that this
part of CLOS must be resolved with the rest of Common Lisp.
This issue has two proposals, NAMED-BY-LIST and COMPUTE-UNDERLYING-NAME.
The proposals differ only in how setf functions are named; the common part
is given first.
PROPOSAL (SETF-FUNCTION-VS-MACRO:COMMON-PART):
Add the concept of a "SETF function". Right now, Common Lisp has two
ways to define the behavior of SETF of a form, DEFINE-SETF-METHOD and
DEFSETF. Terminology:
- a "setf macro" is something that produces code (or other
specifications, as in define-setf-method) which, when evaluated,
will perform the effect of an invocation of setf.
- a "setf function" is something that is called to perform
directly the effect of an invocation of setf.
Extend the set of valid "place specifiers" as defined on CLtL p 94-97
by adding a clause after the existing ones, to the effect that:
"For any other place specifier, the form
(SETF (-name- a1 a2 ... an) new-value),
will expand into a call to -name-'s corresponding setf-function,
such expansion to be of the form:
(<setf-function-for--name-> y a1 a2 ... an), except that
the left-to-right evalution order of a1 a2 ... an y is preserved."
A setf function receives the new value to be stored as its first
argument. The setf function for -name- should have one more required
parameter than -name-, the first required parameter is the new value
to be stored, and the remaining paramters should be the same as -name-'s
parameters. A setf function should return its first argument, since
SETF is defined to return the new value.
Note that SETF will no longer signal any "unknown place specifier"
errors during macroexpansion, because the default behavior is to
simply construct a call to the setf function [except, however, when
"access-fn" isn't legal as the name of a function; for example,
(SETF ((SPAZ) I1 I2) VALUE) is still syntaticly illegal]. But if
at run time, the "setf function" still hasn't been defined as a
function [such as by DEFUN, DEFMETHOD, setf of SYMBOL-FUNCTION etc.],
then a run-time "undefined function" error may occur.
Normally one does not define both a setf function and a setf macro
for the same reading function.
Normally one defines a local reading function and a local setf function
together in a single FLET or LABELS.
In the absence of any setf macro definition, SETF of a function expands
into a call to the setf function. This means that the setf function
only needs to be defined at run time, not compile time.
What CLtL says about (documentation foo 'setf) will not change.
Specifically, the setf documentation type applies just to defsetf (and
define-setf-method, that's an omission in CLtL). The documentation for
a setf function can be retrieved by (documentation '(setf foo) 'function).
The two proposals differ in the manner in which the "name" of
a setf function is determined.
The proposal COMPUTE-UNDERLYING-NAME allows these to be
implementation-dependent, and adds two functions,
UNDERLYING-NAME and UNDERLYING-NAME-TO-SPEC, for converting
from specifications of the form (SETF -name-) to the real
name and back.
The proposal NAMED-BY-LIST specifies that the name of
the setf function for -name- *is* the list (SETF -name-),
and extends those places in Common Lisp that deal with
function names to require them to accept such lists.
PROPOSAL (SETF-FUNCTION-VS-MACRO:COMPUTE-UNDERLYING-NAME):
The name of the setf function for -name- is implementation
dependent; in some implementations, it could be a list (as
with the NAMED-BY-LIST proposal below); in other
implementations, it could be a symbol.
- Add a new function, UNDERLYING-NAME of one argument:
(i) on any list like (SETF <name>), it returns a unique,
implementation-dependent name suitable for actual use as
a function name in that implementaion.
(ii) on symbols, it is the identity function; and
(iii) on any other data, it is undefined; however, other lists
like (<spec-kind> ...) should be reserved for extensions
which the x3J13 committee may be considering.
- Add a new function, UNDERLYING-NAME-TO-SPEC of one argument:
(i) on any argument which results from UNDERLYING-NAME applied
to a list (SETF <name>), UNDERLYING-NAME-TO-SPEC returns
an EQUAL list.
(ii) on any symbol or list not covered by part (i),
UNDERLYING-NAME-TO-SPEC returns it's argument.
(iii) on any other data, it is undefined.
The result of UNDERLYING-NAME should be constant across incarnations
of the same release of an implementation, and should be of a data type
that can be printed out and read back in reliably.
For example, an implementation might choose to define:
(UNDERLYING-NAME '(SETF FOO)) ==> SETF:4.USER.FOO
(UNDERLYING-NAME-TO-SPEC 'SETF:4.USER.FOO) ==> (SETF FOO)
or an implementation could choose to allow lists as names:
(UNDERLYING-NAME '(SETF FOO)) ==> (SETF FOO)
(UNDERLYING-NAME-TO-SPEC '(SETF FOO)) ==> (SETF FOO)
-- Modify the wording in the standard which describes parts imported
from the CLOS specification so as not to imply that lists are
suitable as function names. In particular,
(a) phrases like "... if <function-specifier> names a function" should
be changed to a phrase like "... if <function-specifier> refers to
a defined function", or possibly even something like
"... if (UNDERLYING-NAME <function-specifier>) names a function"
(b) phrases like "(FBOUNDP <function-specifier>)" should be changed
into "(FBOUNDP (UNDERLYING-NAME <function-specifier>))"; or
else other terminology should express the intent of what is to
be said. For example, instead of saying: "When (FBOUNDP <f-s>)
is true ..." one could just as well say "When <f-s> refers to
a defined function ..." The choice of which of these two formats
to use is an editorial one.
(c) phrases like "(SYMBOL-FUNCTION function-specifier)" should be changed
into "(SYMBOL-FUNCTION (UNDERLYING-NAME <function-specifier>))";
or else other terminology should express the intent of what is to
be said. For example, one might say "... the function referred
to by <f-s>". The choice of which of these two formats to use is
an editorial one.
Note that forms like
(DEFMETHOD (SETF FOO) ...)
will expand similarly to
(DEFMETHOD #.(UNDERLYING-NAME '(SETF FOO)) ...)
except that the implicit block name surrounding the body of the method
will be FOO and not (UNDERLYING-NAME '(SETF FOO)).
The underlying call to ADD-METHOD will see the real function name used
for the updator function. The user-level interface of CLOS can still
present the list format as acceptable; it is only the implementation of
DEFMETHOD, DEFGENERIC, that will have to worry about converting to a
"real" name.
In an implementation note, point out that UNDERLYING-NAME-TO-SPEC
could be used by debuggers to print out something more user-comprehensible
than the internal names that an implementation might use.
PROPOSAL (SETF-FUNCTION-VS-MACRO:NAMED-BY-LIST):
The "name" of the setf function for -name- is a list (SETF -name-),
where -name- is a symbol. The body of this
function is surrounded by an implicit block named -name-.
Modify the following functions, macros, special forms, and declarations:
COMPILE function, DEFUN macro, DISASSEMBLE function, DOCUMENTATION function,
FBOUNDP function, FLET special form, FMAKUNBOUND function, FTYPE declaration,
FUNCTION special form, FUNCTION declaration, INLINE declaration,
NOTINLINE declaration, LABELS special form
to accept such lists in addition to symbols as function names, so that
setf functions can be defined and manipulated.
Note that, although it is possible to lexically redefine the function definition
of (SETF -name-), the macro expansion of SETF is not affected by such
lexical redefinition.
Clarify that (documentation foo 'setf) is only affected by invocations
of defsetf and define-setf-method. (As specified above, the DOCUMENTATION
function is modified so that (documentation '(setf foo) 'function) will
retrieve documentation strings established by
(defun (setf foo) (arg) "This is documentation..." ....)
Examples:
;;If SETF of SUBSEQ were not already built into Common Lisp,
;it could be defined, under NAMED-BY-LIST, as:
(defun (setf subseq) (new-value sequence start &optional end)
(unless end (setq end (length sequence)))
(setq end (min end (+ start (length new-value))))
(do ((i start (1+ i))
(j 0 (1+ j)))
((= i end) new-value)
(setf (elt sequence i) (elt new-value j))))
;; while with COMPUTE-UNDERLYING-NAME, as:
(setf (symbol-function (underlying-name '(setf subseq)))
#'(lambda (new-value sequence start &optional end)
(unless end (setq end (length sequence)))
(setq end (min end (+ start (length new-value))))
(do ((i start (1+ i))
(j 0 (1+ j)))
((= i end) new-value)
(setf (elt sequence i) (elt new-value i)))))
;;Another example, showing a locally defined setf function.
;; with NAMED-BY-LIST:
(defun frobulate (mumble)
(let ((table (mumble-table mumble)))
(flet ((foo (x)
(gethash x table))
((setf foo) (new x)
(setf (gethash x table) new)))
..
(foo a)
..
(setf (foo a) b))))
;; with COMPUTE-UNDERLYING-NAME:
; First, define MIDDLE-REF to be an accessor function as follows:
(defun middle-ref (vec i)
(check-type i fixnum)
(aref vec (ceiling i 2)))
;; then, even given:
(defun #.(underlying-name '(setf middle-ref)) (new-element vec i)
(check-type i fixnum)
(setf (aref vec (ceiling i 2)) new-element))
(flet ((#.(underlying-name '(setf middle-ref)) (new-element vec i)
;;"wide-body" version of set-middle-ref
(declare (ignore i))
(fill vec new-element :end (ceiling i 2))))
(setf (middle-ref "abc" 1) #\z))
;;;GET-SETF-METHOD could implement setf functions by calling
;;;this function when none of the other rules of CLtL p 94-7 apply:
(defun get-setf-method-for-setf-function (form)
(let ((access-fn (car form))
(new-value (gensym))
(temp-vars (do ((a (cdr form) (cdr a))
(v nil (cons (gensym) v)))
((null a) v))))
(values temp-vars (cdr form) (list new-value)
`(,@
#+NAMED-BY-LIST
`(funcall #'(setf ,access-fn))
#+COMPUTE-UNDERLYING-NAME
`(,(underlying-name `(setf ,access-fn)))
,new-value ,@temp-vars)
`(,(car form) ,@temp-vars))))
RATIONALE:
Both proposals allow a more functional appropach to dealing with
SETF, and bring the CLOS and Common Lisp parts of the standard into
more alignment. COMPUTE-UNDERLYING-FUNCTION does so by adding functions
to go between the "specification" of (SETF -name-) to the name
actually used, while NAMED-BY-LIST does so by extending a number
of already existing Common Lisp functions, macro and special forms.
SETF functions take the "new value" as the first argument to allow
for defining them on accessors that have &REST and &KEY arguments.
By making the names and arguments of setting functions explicit, CLOS is
considerably simplified. In addition, this can supersede any proposals
to introduce a lexically local form of defsetf; lexically local setf
functions serve the same needs.
Current code that resembles
(defsetf foo |setf FOO|)
(defun foo (x) ..)
(defun |setf FOO| (x new) ..)
or
(defsetf foo internal-foo-setter)
(defun foo (x) ..)
(defun internal-foo-setter (x new) ..)
can be, but is not required to be, replaced with the following code
(defun foo (x) ..)
(defun (setf foo) (new x) ..)
An advantage of this is that several disparate styles of using
DEFSETF can be replaced with a single common style of using
setf functions, making programs more standardized and readable.
CURRENT PRACTICE:
A few Common Lisp implementations already have a feature similar,
but not compatible with, NAMED-BY-LIST, in that they allow setting
functions named (SETF reader). We don't know of any implementation
that has precisely the proposed feature.
For example, This will be an incompatible change for Symbolics,
since it already has setf functions but they do not take the
same arguments as proposed here.
COST TO IMPLEMENTORS:
For either proposal, the SETF macro expansion would have to be
extended to generate the appropriate call on the setf function.
The additional cost of proposal COMPUTE-UNDERLYING-NAME is low;
implementations would need to add the two proposed functions.
In addition, implementations would need to modify their
CLOS implementation to use UNDERLYING-NAME where appropriate.
[A sample implementation is given at the end of the proposal.]
The cost of proposal NAMED-BY-LIST is higher, since many other
functions, macros and special forms would have to be extended to
deal with the SETF specifications. The main cost is generalization
of a few functions to accept lists beginning with SETF where they
now accept only symbols. Implementations must add a data structure
to store the function definition of a setf function, however, this can
be done with property lists or generated symbols.
COST TO USERS:
Both proposals are basically upward-compatible changes for currently
portable Common Lisp programs.
As with any language extension, some program-understanding programs may
need to be enhanced. A particular issue with NAMED-BY-LIST is programs
that assume that all function names are symbols. They may use GET to
access properties of a function name or use EQ or EQL (perhaps via
MEMBER or ASSOC) to compare function names for equality. Such programs
will need improvement before they can understand programs that use
the new feature.
Both proposals remove some macro-expansion error checking, since
detection of incorrect SETF expansion will be postponed to run-time.
This is a minor deficiency, as it puts invocation of SETF functions
on the same footing as other function calls.
COST OF NON-ADOPTION:
Common Lisp and CLOS would be significantly inconsistent; some major
rethinking of CLOS would be required.
BENEFITS:
Improve usability of SETF. Avoids cost of non-adoption.
With NAMED-BY-LIST, current code that resembles
(defsetf foo |setf FOO|)
(defun foo (x) ..)
(defun |setf FOO| (x new) ..)
or
(defsetf foo internal-foo-setter)
(defun foo (x) ..)
(defun internal-foo-setter (x new) ..)
could be replaced with the following code
(defun foo (x) ..)
(defun (setf foo) (new x) ..)
With NAMED-BY-LIST, a convenent way of lexically defining
or redefining the behavior of SETF would be allowed.
PERFORMANCE IMPACT:
Negligible; ether proposal might make an implementation slightly
larger.
ESTHETICS:
SETF would be more esthetic, but less powerful, if it had only the
proposed setf functions and did not have setf macros. Such a major
incompatible change is of course out of the question; however, if setf
functions are stressed over setf macros, SETF will be much easier to
teach.
The proposal NAMED-BY-LIST allows lists of the form (SETF -name-) to be
used in many places where only symbols were allowed before. Some
people feel this is unesthetic, because it is at odds with many
current descriptions of Common Lisp say that names are symbols.
DISCUSSION:
This issue has been in discussion in the CLOS committee and at X3J13
for over a year. Versions of the proposal were distributed, discussed,
and voting tabled at three successive meetings of X3J13. The proposal
previously distributed most resembles the NAMED-BY-LIST proposl contained
in this version.
Note that in Common Lisp, setf macro expansion is an operation on
function names, not on functions. It differs from some dialects of
Scheme, such as T, in this respect. Neither proposal attempts to
change that aspect of Common Lisp.
Most of the objection to previous proposals were based on
introducing the notion that the name of the
setf-function associated with FOO should be a list, (SETF FOO). This is
a considerable extension to the idea of a "function name", at least for
standard Common Lisp implementations that do not implement Lisp machine
style function-specs.
The CLOS group unsuccessfully tried a number of alternatives that
did not require naming the setf function at all. However,
fundamentally the problem is that there has to be a name that the user
uses to define the thing and to talk about it. Trying to hide the name
just means you use a more obscure name, like an alternate syntax for
DEFUN or DEFUN-SETF. Another reason for making the name explicit is to
allow one to use FLET for the setf function -- something which would be
difficult if there is not a name-like entity that can be bound.
The following related features were considered but are specifically
not being proposed at this time, since they are unnecessary for CLOS
and appear not to improve the simplicity and esthetics of the language:
a) Lexically local setf macros, that is, a cross between DEFSETF and
MACROLET. This does not appear to be logically necessary. Would all
three ways of defining lexically global setf macros need local
counterparts?
b) Define the meaning of defmacro or macrolet of (setf foo)?
This would be a fourth way to define a setf macro.
c) Enhance the definition of global setf macros, for example to
say that (MACRO-FUNCTION '(SETF FOO)) returns an expander function
that takes two arguments and returns five values.
!
Appendix: Sample implementation of COMPUTE-UNDERLYING-NAME
The following code can be used by an implementation which doesn't
have "function specs" to implement the COMPUTE-UNDERLYING-NAME proposal:
;;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: SYSTEM; Base: 10 -*-
;;;
;;; Author: JonL White, 15-Nov-88
;;;
(in-package "SYSTEM") ;or, your development package
(eval-when (eval compile load)
;;; The SETF package should be reserved for this purpose
;;;
(or (find-package "SETF")
(make-package "SETF" :use nil))
(defparameter *setf-package* (find-package "SETF"))
(unless (and (null (package-use-list *setf-package*))
(null (package-used-by-list *setf-package*)))
(error "SETF package has connections?"))
;;; "Internal Markers", to be used for uninterned symbols.
;;;
(export (intern "SETF-SPEC" *setf-package*) *setf-package*)
(export (intern "SETF-NAME" *setf-package*) *setf-package*)
)
(eval-when (eval compile)
(defmacro setf-spec-p (x)
(let ((spec (gensym)))
`(LET ((,spec ,x))
(AND (CONSP ,spec)
(EQ (CAR ,spec) 'SETF)
(CONSP (CDR ,spec))
(NULL (CDDR ,spec))
(SYMBOLP (SECOND ,spec))))))
)
(defun UNDERLYING-NAME (spec)
(cond
((symbolp spec)
spec)
((setf-spec-p spec)
(let* ((accessor (second spec))
(accessor-name (symbol-name accessor))
(home-package (symbol-package accessor)))
(if home-package
(let* ((package-name (package-name home-package))
;; 'spec-name' is a form like "~D.~A.~A", but FORMAT has a
;; problem with global print parameters like *print-radix*
(spec-name (concatenate 'string
(write-to-string (length package-name)
:radix nil :base 10 :length nil :level nil)
"."
package-name
"."
accessor-name))
(updator (or (find-symbol spec-name *setf-package*)
(let ((sym (intern spec-name *setf-package*)))
(export sym *setf-package*)
sym))))
;; A possible optimization, which trades off space for time, is
;; as follows; see definition of UNDERLYING-NAME-TO-SPEC below
;;(setf (get updator 'setf:setf-spec) (copy-list spec))
updator)
(or (get accessor 'setf:setf-name)
(let* ((uname (concatenate 'string "SET-" accessor-name))
(updator (make-symbol uname)))
(setf (get accessor 'setf:setf-name) updator)
(setf (get updator 'setf:setf-spec) (copy-list spec))
updator)))))
(t
(error "~S is an invalid arg for ~S" spec 'UNDERLYING-NAME))))
(defun UNDERLYING-NAME-TO-SPEC (x)
(cond
((not (symbolp x))
(if (setf-spec-p x)
x
(error "~S is an invalid arg for ~S"
x 'UNDERLYING-NAME-TO-SPEC)))
((get x 'setf:setf-spec))
(t
(let ((home-package (symbol-package x)))
(if (not (eq home-package *setf-package*))
x
(let ((name (symbol-name x))
accessor package-name)
;; Unpack the name, which is a form like "~D.~A.~A"
(multiple-value-bind (nchars starti)
(parse-integer name :radix 10 :junk-allowed t)
(incf starti)
(setq package-name (subseq name starti (incf starti nchars)))
(incf starti)
(setq accessor (find-symbol (subseq name starti)
(find-package package-name)))
(unless accessor
(error "~S failed to parse in ~S"
x 'UNDERLYING-NAME-TO-SPEC))
`(SETF ,accessor))))))))
----- End Forwarded Messages -----