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

Issue: SETF-PLACES (version 1)



The following is the emmendation of SETF-FUNCTION-VS-MACRO that was
discussed at the Fairfax meeting; it incorporates the suggestions  
made there by Gregor, Bob Kerns, and myself.  Eric Benson and Patrick
Dussud here at Lucid have also reviewed it.
 
I've started it out under a different issue name, since it is such
a drastic change; but I wouldn't object at all if someone wants it
to be just another version of SETF-FUNCTION-VS-MACRO.

-- JonL --

!

Issue: SETF-PLACES

References: SETF rules for what a place-specifier can be; CLtL pp.94-7
            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

Related issues: SETF-FUNCTION-VS-MACRO

Category:  ADDITION

Edit history:  Version 1, 11-Nov-88, by JonL


Problem description:

Common Lisp explicitly refrains from giving names to accessor update
functions.  The intent is that the macro SETF should shield the user
 from ever having to know such names; the correlation between an accessor
name and its corresponding updator name, or updating code sequence, is
to be established by DEFSETF and DEFINE-SETF-METHOD.  Update function
names like SET and RPLACA are retained primarily for backwards
compatibility.  See CLtL p.93-4.

However, this is extremely inconvenient for CLOS programing.  The rub 
is that the functionality of an updator function must be specifiable 
"in pieces", by incremental uses of DEFMETHOD distributed throughout 
perhaps dozens of files.  A single definition using either DEFSETF or
DEFINE-SETF-METHOD is not an acceptable constraint for this style.
A named, generic function is the CLOS interface for doing distributed
code specification.

Furthermore, it is not at all clear where a DEFSETF call for a generic
function should go.  Should it be before the first DEFMETHOD on the 
update function?  should it be bundled into every such DEFMETHOD?  
should it be bundled into ENSURE-GENERIC-FUNCTION?  Clearly, the first 
two options would violate the modularity CLOS has strived so hard to 
achieve; and the third violates the optionality of ENSURE-GENERIC-FUNCTION.
The best choice would be to elide the DEFSETF call completely.  Some 
way is needed to designate an update function, without necessarily 
doing a DEFSETF or DEFINE-SETF-METHOD first.

Additionally the simpler form of DEFSETF, which could be used for
almost all generic needs (e.g., slot updating), requires the new-
value argument to be the last argument to the update function.
But in order to be able to discriminate upon the class of the
new-value argument, it cannot be described simply as "last" --
it must come before any &optional and &key arguments.  Thus there
is need for some other avenue whereby the new-value argument would
come, say, first in the argument list of the update function.

Finally, the CLOS specification X3J13 88-002R seems to imply
that the CLOS exterior syntax for function specifiers must be 
implemented down in the innards of every conforming Lisp 
implementation.  There is a very large amount of resistance
in the X3J13 group, at present, to any proposal which requires
any non-symbols as ordinary function names; not only do people
object to code like (SYMBOL-FUNCTION '(SOME LIST)), but there
is great reluctance to carry out system-wide modifications to
all places that deal with function names (most of which now
presume that "name" means SYMBOL).  Yet is the current opinion
of most of the CLOS subcommittee that the exterior CLOS interface
can be kept intact without requiring the underlying Lisp 
implementation to support non-symbols as function names.


Proposal (SETF-PLACES:ADD-SETF-FUNCTIONS)

-- Specify that certain function names are reserved to be "SETF functions",
   or "updator functions", for use by SETF expansions.  The very existence 
   of such an updator function is implicitly similar to having done a 
   DEFSETF [or rather, a modified form of the simple DEFSETF, as explained 
   next below].  Every accessor function name is uniquely paired with such 
   an updator name, regardless of whether the updator name ever has a 
   functional definition.  However, these functions do not replace any 
   previously defined SETF methods, nor override the place specifications 
   of CLtL section 7.2; they simply provide a default expansion for SETF, 
   as described below.

-- Specify that such a a SETF function must take one more argument than 
   its corresponding access function.  Let <access-fn> and <update-fn> 
   be such a correlated pair; then when SETF is given a "place" that is 
   a call on <access-fn>, it expands into a call on <update-fn> that is 
   given all the arguments to <access-fn> and also, as its very first 
   argument, the new-value (which must be  returned by <update-fn> as 
   its value).  For example, suppose that ASET is the updator function
   name corresponding to AREF.  Then 
        (SETF (AREF a i1 i2 ... in) value)
   could expand into
        (ASET value a i1 i2 ... in)

-- Extend the set of valid "place specifiers" as defined on CLtL p.94-7 
   by adding the following clause after all the existing ones:
       For any other place specifier, the form
         (SETF (<access-fn> A1 A2 ... AN) NEW-VALUE)
       will expand into a call to the uniquely-named updator function
       corresponding to <access-fn>, that is to the function named by
       (UNDERLYING-NAME '(SETF <access-fn>)).
   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.

   Note also that not every SETF method for an accessor function can be
   defined using an updator function.  For example, LDB cannot be handled
   this way, even if its corresponding update name is a defined function;
   LDB must be handled by DEFINE-SETF-METHOD, and as such the prior rules
   of CLtL p.94-7 would apply.

-- Be reminded that the rules for interpreting SETF place specifiers
   are actually embodied in the functions GET-SETF-METHOD and
   GET-SETF-METHOD-MULTIPLE-VALUE, rather than in the SETF macro
   itself.  Thus these two functions must be altered to reflect the new 
   "place specifier" called for just above.  Since the rules on p.94-7 
   of CLtL are to be applied in order, then SETF functions will only be 
   used when no SETF "method" has been defined for the name, such as by 
   calling DEFINE-SETF-METHOD or DEFSETF; also, a macro definition for 
   the access name will block the use of the SETF function, since the 
   macro call must be expanded first.  Remember also that such a updator 
   name may have a lexically local definition, as well as (or in addition
   to) a global one.

-- Add a new function, UNDERLYING-NAME of one argument; and also add an
   inverse for this function, UNDERLYING-NAME-TO-SPEC of one argument.
   UNDERLYING-NAME is defined as:
      (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.
   UNDERLYING-NAME-TO-SPEC is defined as:
      (i) on any argument which specifically is the output of part (i)
          above [i.e., an "underlying" name], it returns (SETF <name>);
          thus the argument is the unique name which is EQUAL to
          (UNDERLYING-NAME '(SETF <name>)).
     (ii) on any symbol or list not covered by part (i) just above, it 
          returns it's argument.
    (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.

   The reason EQUAL is the determiner of "uniqueness" above is that it
   is EQ for symbols; and for implementations which have "function specs"
   it permits non-EQ copies of (SETF <name>) to be used interchangably.

   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)

-- Alter all the above-referenced documentation in 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.

Since the concept of a standard expansion for DEFMETHOD has been
accepted, then it is clear that a form like 
    (DEFMETHOD (SETF FOO) ...)
will expand exactly the same as
    (DEFMETHOD #.(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.

One expected use of UNDERLYING-NAME-TO-SPEC is in Lisp-level debuggers,
which could try to print out something more user-comprehensible than
the very internal names that an implementation might use in place of
function specs.



Examples:

;;; If CLtL did not already prescribe a SETF expansion for SUBSEQ calls,
;;;  it could be defined like this:

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

or, for implementations that have "function specs", this could be writen:

  (defun (setf subseq) (new-value sequence start &optional end)
    . . .)


;;; Here's an example using a local function.  First, define 
;;;  MIDDLE-REF to be an accessor function as follows.  [Assume 
;;;  also that MIDDLE-REF's home package is BAR.]

    (defun middle-ref (vec i) 
      (check-type i fixnum)
      (aref vec (ceiling i 2)))

;;; Now let SETF:3.BAR.MIDDLE-REF be the (implementation-dependent) 
;;;  updator function name corresponding to MIDDLE-REF; a normal 
;;;  definition of an update function for MIDDLE-REF could be:

    (defun setf:3.bar.middle-ref (new-element vec i) 
      (check-type i fixnum)
      (setf (aref vec (ceiling i 2)) new-element))

;;; But the SETF below will call FILL, because of the local definition 
;;;  of SETF:3.BAR.MIDDLE-REF;  and nowhere have we have made any
;;;   explicit call to DEFSETF or DEFINE-SETF-METHOD for MIDDLE-REF.

    (flet ((setf:3.bar.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))


;;; The following function could be called by GET-SETF-METHOD, to 
;;;  implement SETF functions, when none of the other rules of CLtL
;;;  p.94-7 apply.

  (defun get-setf-method-for-setf-functions (form)
    (let* ((new-value (gensym))
	   (temp-vars (do ((a (cdr form) (cdr a))
			   (v nil (cons (gensym) v)))
			  ((null a) v)))
	  ((access-fn (car form)))
	  ((update-fn (underlying-name `(SETF ,access-fn)))))
      (values temp-vars 
	      (cdr form) 
	      (list new-value)
	      `(,update-fn  ,new-value  ,@temp-vars)
	      `(,access-fn ,@temp-vars))))

;;; For those implementations using "function specs", the form:
;;;   `(,update-fn  ,new-value  ,@temp-vars)
;;;  would probably have to  be replaced by:
;;;   `(FUNCALL #',update-fn  ,new-value  ,@temp-vars)



Rationale:

The paragraphs of the "Problem description:", except for the first,
describe four major problems with the status quo -- three concerning
the unsuitability of current SETF methods for supporting the CLOS
generic style, and one for an unintended presumption that every
implementation of CL will have "function specs".  This proposal
corrects these problems, without adding any significant new ones.


Current practice:

Some implementations have "function specs", so that forms like 
(SETF FOO) are permitted to name functions;  but none have extended 
the setf place specifiers as proposed herein.

Cost to Implementors:

Basically, none.  Implementations which already have Lisp Machine 
style "function specs" can just define UNDERLYING-NAME and
UNDERLYING-NAME-TO-SPEC as the identity function.  For those without
such capabilities, there is a portable implementation listed in the 
discussion section.

Extending GET-SETF-METHOD etc. to handle SETF functions should be a
very modest task at most.

Cost to Users:

This is basically an upward-compatible addition, so there should be
no cost to users [at least not for correct programs -- incorrect
SETF expansions will no longer be signalled at macroexpand time,
but may simply result in a runtime error for undefined function.]

Cost of non-adoption:

Non-adoption of this proposal would be a significant setback for the 
Common Lisp Object System.  There seems to be no agreeable alternative
for implementing generic setf methods.

Performance impact:

N.A.

Benefits:

See "Cost of non-adoption".

Esthetics:

This proposal increases the size of the definition of SETF; but
it greatly simplifies the "default" case, namely just defining
an updator function to correspond to an accessor.



Discussion:


The following code can be used by an implementation which doesn't
have "function specs" to implement the new functions:

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