[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Issue: SETF-FUNCTION-VS-MACRO (version 5)



This is an attempt to merge version 3 (from David Moon) and Version 4 (from JonL White, distributed as SETF-PLACES version 1), into a single Issue with two Proposals.

Doing so has not been easy. I must admit that, having gotten a first draft, I'm eager to
 mail this out for comments even without having proofread it myself carefully. I'll
do so this week, but if you have some objection to the overall merger, please speak up.

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

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), but 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.
   
Thus in one implementation, which uses only symbols to name functions,
it might be that:
      (UNDERLYING-NAME '(SETF FOO)) ==> SETF:4.USER.FOO
      (UNDERLYING-NAME-TO-SPEC 'SETF:4.USER.FOO) ==> (SETF FOO)
whereas in another implementation, which has LispMachine style
"function specs", it would be that:
      (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.

Thus, #'(setf foo) should have one more required parameter
than #'foo, the first required parameter is the new value to be stored,
and the remaining parameters should be the same as #'foo's parameters.

A setf function must return its first argument, since setf is defined
to return the new value.

A definition of a setf function can be lexically local, like a
definition of a reading function.  The following rules specify the
behavior of SETF; note that these rules are ordered and the first rule
to apply supersedes any later rules.  These rules are a consistent
extension of the current behavior of Common Lisp and the Cleanup
committee's resolution of issue GET-SETF-METHOD-ENVIRONMENT.  Only
rule 4 is new with this proposal.

Rules for the macroexpansion of (setf (foo x) y):

(1) If the function-name foo refers to the global function definition,
rather than a locally defined function or macro, and if there is a
setf macro defined for foo, use the setf macro to compute the expansion.

(2) If the function-name foo is defined as a macro in the current scope,
use macroexpand-1 to expand (foo x) and try again.

(3) If the function-name foo is defined as a special form in the current
scope, signal an error.

(4) Expand into the equivalent of
    (let ((#:temp-1 x)          ;force correct order of evaluation
          (#:temp-2 y))
      (funcall #'(setf foo) #:temp-2 #:temp-1))

Note that rule 4 is independent of the scope of the function name
(setf foo).  It does not matter if that scope is different from the
scope of the function name foo.  This allows some nonsensical programs
to be written, but does not seem harmful enough to justify making more
complicated rules to compare the scopes of the two function definitions.

The above rules are actually implemented by GET-SETF-METHOD and
GET-SETF-METHOD-MULTIPLE-VALUE, rather than by the SETF macro itself.
Thus GET-SETF-METHOD generates the appropriate five values for a form
that is not a macro-invocation and does not have a defined setf macro.

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, as for any function, is retrieved by
(documentation '(setf foo) 'function).

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