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

Issue: SETF-SUB-METHODS (Version 2)



    Date: Tue, 24 May 88 01:36:12 PDT
    From: Jon L White <edsel!jonl@labrea.stanford.edu>

I have read this proposal carefully and I generally approve the approach
of SETF-SUB-METHODS:DELAYED-ACCESS-STORES, however it needs some more work.
The test cases are inadequate (easily corrected), and the proposal is
incoherent in the sense that what it proposes for LDB and what it
proposes for GETF are not consistent, I think.  Also modify-macros
such as INCF are involved with this issue as well.  Comments follow.

      The forms listed at the top of CLtL p96 permit recursive <place>
      specifiers; for each one, we will describe how the sub-recursive 
      information from GET-SETF-METHOD is used.

You left out MASK-FIELD and CHAR-BIT.  I assume you mean for MASK-FIELD
to do the same as LDB; you should say so explicitly.  The steps for
CHAR-BIT should be written out explicitly since the arguments are not
the same as for LDB.

	In a form such as

	(SETF (LDB <byte-spec> <place-form>) <value-form>)

	the place referred to by the <place-form> must always be both
	accessed and updated.  

This assumes that zero-bit bytes may not be optimized out.  We could
either say that, or say that it is unspecified whether the <place-form>
is updated if the value is not changed.  The value could be unchanged
because the byte has zero width or because the value being stored is
equal to the current value.  I mildly prefer the former (see analogous
issue with GETF below).

I agree with your step by step explication of SETF of LDB (omitted
 from this reply).

	The case of GETF is complicated by the fact that two different
	"place" locators are involved: one to use if the specified
	indicator is present in the property list, and another if not.
	For example, in (SETF (GETF (AREF ... I) 'B) 6), if the I'th slot
	of the array is NIL, then that slot must be changed, but if it
	contains a list with the property B then only the cons cell with
	that property value needs to be changed.

I strongly disagree with this.  I think it's wrong to specify that
SETF of GETF does not store into the <place> if the value that would
be stored there happens to be EQ to what is already there.  Common Lisp
should either leave it unspecified, or require that the store always
take place (I prefer the latter).  Some might argue that requiring the
store always to take place eliminates a potential performance optimization,
but I suspect that it is more efficient to do the store than to check
whether it needs to be done.

The reasons it matters whether the store happens when the value isn't
changing are twofold.  First, there is the example in the problem statement
for this issue, which leaves a different value in the variable R if
the store is optimized out when the property is already present.  Second,
the store can involve calling a user-defined function which might have
other side-effects.  This can happen with DEFSETF and is even more likely
with CLOS use of SETF that invokes methods.  I think it's better to say
that a SETF always stores into <place>, even if the value being stored is
EQ to what is already there, and even if SETF also does other side-effects.

The general rules that I would propose to appear in the documentation
for SETF of a function whose argument is a <place> would be either:

  Evaluations are performed left to right.
  The <place> is not read until all evaluations have been performed.
  The <place> is always written.

or:

  Evaluations are performed left to right.
  The <place> is read at the time it would be read if
   the <place-form> were evaluated.
  The <place> is always written.

For choosing between these two possible sets of rules, see discussion below.

	More specifically, the expansion of 

	(SETF (GETF <place-form> <ind-form>) <value-form>)

	should generate code....

Instead of what JonL proposed, I prefer the following:

	1. Bind the temporaries for <place-form> 
	2. Evaluate <ind-form>
	3. Evaluate <value-form>
	4. Do the access to <place-form>
	5. Use the results from 2, 3, and 4 to modify the property list
        6. Do the store into <place-form> of the updated property list

Deferring the access to <place-form> until after all the evaluations is
consistent with what JonL proposed for LDB.  So is only accessing
<place-form> once.  However, is the behavior this produces intuitive?
See test case below.

    Test Cases:
	  (setq integer #x69)
	  (rotatef (ldb (byte 4 4) integer) (ldb (byte 4 0) integer))

This test case is inadequate, since it does not specify what the
intended result is.  May I suggest adding:

          integer => #x96

assuming that that is the result you intend to propose as correct.

This test case is also inadequate since no test cases for the other
affected functions were included.  May I suggest:

          (setq integer #x69)
	  (setf (mask-field (byte 4 4) integer) (incf integer)) => #x6A
          integer => #x6A
	  (setf (mask-field (byte 4 4) integer) (ash (incf integer) 4)) => #x6B0
          integer => #xBB

          (setq l (list #x69))
          (rotatef (ldb (byte 4 4) (car l)) (ldb (byte 4 0) (car l)))
          l => (#x96))

          (setq char (make-char #\A 1)) => #\c-A
	  (rotatef (char-bit char :control) (char-bit char :meta))
	  char => #\m-A

	  (setq r '(a 1 b 2 c 3))
	  (setq s r)
	  (setf (getf r 'b) (progn (setq r nil) 6)) => 6
          r => (b 6)		;result from what I proposed above
          s => (a 1 b 2 c 3)
	  r => nil		;JonL's result
	  s => (a 1 b 6 c 3)
          r => (a 1 b 6 c 3)	;result from always storing, but doing
          s => (a 1 b 6 c 3)	;the access in the usual order

	  (setq a (vector 1))
          (incf (aref a 0) (incf (aref a 0)))
          (aref a 0) => 4	;result if access happens after eval
          (aref a 0) => 3	;result if access happens in usual order

I guess the char-bit test case won't run in implementations that don't
have both of those char bits.

For the getf test case I have shown three possible results, and for
the incf test case I have shown two.  We have to decide which we want.

    Current Practice:

     -- Xerox and Franz already operate on GETF according to this perscription.
     -- Symbolics and Lucid differ by always doing a setf on the variable
	rather than updating the appropriate cons cell of the property list; 
	additionally, they fail to connect the new value put into 'r' to the
	original property list which was 'r's value initially.

I can't figure out what you mean by the above.  Symbolics Genera 7.2
certainly does update the appropriate cons cell of the property list and
certainly does modify s's value; it produces the third of the three
results I've listed for the test case.

     -- HP and VAX  Common Lisps update the cons cell, but then set the variable
	'r' again, nullifying the effect of the "(setq r nil)" in the <value-form>
	computation.

    Discussion:

     There is an interesting parallel between this case for GETF and the
     very common mistake made by Lisp programmers with respect to the 
     function DELETE.  How often the complaint is filed that DELETE didn't
     do anything when it should have; but in fact the filer simply forgot
     that delete can either update some CAR slot of the list originally 
     given it, or return some tail of the list originally give it, but
     not both!

Not true.  For example, (delete 'a '(a a b a d)) does both (assuming
I correctly understand what you meant by the typo "update some CAR slot").
I agree that there is a parallel.

     A previous proposal to fix this problem was misdirected.  It was
     phrased as follows:

	  Proposal: SETF-METHOD-FOR-SYMBOLS:TEMPORARY-VARIABLE

Agreed.

     Implicitly, two "places" are being specified here to be updated; but
     in fact the two places are not independent -- they both involve setq'ing
     the variable 'integer'.  Furthermore, each store operation implicitly
     requires fetching the value from that place in order to combine it
     with DPB.  It is necessary to delay the accesses until the last moment
     before combining with DPB in order to see the side-effects of the
     earlier store operations.

Agreed, except that the test cases above show that it's not so clear-cut
that we want to delay the accesses.

Anyone have any ideas of the direction out of this morass?