From: Mark McConnell
Subject: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <4r9ec3$2a6@news.cis.okstate.edu>
I would like to use a form (setf-when-null place value) that changes the
value of place only if that value is nil.  If place is not null, the value
form should not be evaluated, and place should not be changed.

So far I have

(defmacro setf-when-null (place value)
  `(let ((old-value ,place))
     (when (null old-value)
       (setf ,place ,value))))

But here place is evaluated twice.  If the evaluation has side-effects, this
could be dangerous.  Even in my case, where evaluating place has no
side-effects, my version is wasteful.

Any advice would be appreciated.

From: Kelly Murray
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <4r9ltm$o5i@sparky.franz.com>
In article <··········@news.cis.okstate.edu>, Mark McConnell <·······@math.okstate.edu> writes:
>> (defmacro setf-when-null (place value)
>>   `(let ((old-value ,place))
>>      (when (null old-value)
>>        (setf ,place ,value))))
>> 
>> But here place is evaluated twice.

You are confusing the macro expansion evaluation with runtime evaluation.
As a setf location, place does not get evaluated, but is only a reference
to the location.  The macro is what you want, except for this:

As you're concerned about getting the macro right, 
you shouldn't bind named lexical variables in the expansion
as they may override any bindings outside the scope that
could be referenced by the value form, e.g.

(let* ((x nil) (old-value 2)) (setf-when-null x (list old-value))) 
would return '(nil) and not '(2).

Best to gensym local vars in macroexpansions:

(defmacro setf-when-null (place value)
  (let ((old-value (gensym)))
   `(let ((,old-value ,place))
      (when (null ,old-value)
        (setf ,place ,value))))))


-Kelly Murray  ···@franz.com
From: Barry Margolin
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <4rc668$ipg@tools.bbnplanet.com>
In article <··········@sparky.franz.com>, Kelly Murray <···@franz.com> wrote:
>In article <··········@news.cis.okstate.edu>, Mark McConnell <·······@math.okstate.edu> writes:
>>> (defmacro setf-when-null (place value)
>>>   `(let ((old-value ,place))
>>>      (when (null old-value)
>>>        (setf ,place ,value))))
>>> 
>>> But here place is evaluated twice.
>
>You are confusing the macro expansion evaluation with runtime evaluation.
>As a setf location, place does not get evaluated, but is only a reference
>to the location.

No, Mark was right.  While PLACE itself doesn't get evaluated twice,
subforms within PLACE do.  E.g.

(setq i 0)
(setf-when-null (aref array (1+ i)) 3)

will read from element 1 and then (if it was null) assign to element 2!

The simple solution is to use DEFINE-MODIFY-MACRO:

(define-modify-macro setf-when-null (new-value)
  (lambda (old-value new-value)
    (or old-value new-value)))

(Strictly speaking this isn't quite the same as the original code; it
always stores, but it stores back the old value when it's non-null.)

But I suspect this doesn't really answer the original poster's question, as
he would probably like to know *how* DEFINE-MODIFY-MACRO solves the
problem.  The answer is that all these macros make use of GET-SETF-METHOD
(or GET-SETF-METHOD-MULTIPLE-VALUE).  This arranges for the values of
arguments in the place form to be evaluated once into a list, which can
then be passed as arguments both when reading and writing.  In order to
write SETF-WHEN-NULL in its raw form, you would do:

(defmacro setf-when-null (place value &environment env)
  (multiple-value-bind (vars vals stores store-form access-form)
      (get-setf-method place env)
    `(let* ,(mapcar #'list
		    (append vars stores)
		    (append vals (list value)))
       (when (null ,access-form)
         ,store-form))))

This method also solves the macro hygiene problem that Kelly fixed;
GET-SETF-METHOD will return gensyms in the VARS and STORES variables.
-- 
Barry Margolin
BBN Planet, Cambridge, MA
······@bbnplanet.com -  Phone (617) 873-3126 - Fax (617) 873-6351
(BBN customers, please call (800) 632-7638 option 1 for support)
From: Paul Vianna
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <31D912F3.3B2@tribeca.ios.com>
Kelly Murray wrote:
> 
> In article <··········@news.cis.okstate.edu>, Mark McConnell <·······@math.okstate.edu> writes:
> >> (defmacro setf-when-null (place value)
> >>   `(let ((old-value ,place))
> >>      (when (null old-value)
> >>        (setf ,place ,value))))
> >>
> >> But here place is evaluated twice.
> 
> You are confusing the macro expansion evaluation with runtime evaluation.
> As a setf location, place does not get evaluated, but is only a reference
> to the location.  The macro is what you want, except for this:
> 
> As you're concerned about getting the macro right,
> you shouldn't bind named lexical variables in the expansion
> as they may override any bindings outside the scope that
> could be referenced by the value form, e.g.
> 
> (let* ((x nil) (old-value 2)) (setf-when-null x (list old-value)))
> would return '(nil) and not '(2).
> 
> Best to gensym local vars in macroexpansions:
> 
> (defmacro setf-when-null (place value)
>   (let ((old-value (gensym)))
>    `(let ((,old-value ,place))
>       (when (null ,old-value)
>         (setf ,place ,value))))))
> 
> -Kelly Murray  ···@franz.com

But what about a case like:

	(setf-when-null (aref a (incf i)) 42)

Won't (INCF I) be done twice? And isn't there a way around this with
some more complex SETF machinery? (I don't have "On Lisp" handy right
now!)
From: Rolf-Thomas Happe
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <4rbi85$jf9@n.ruf.uni-freiburg.de>
Paul Vianna (·····@tribeca.ios.com) wrote:
[snipped]
: But what about a case like:

: 	(setf-when-null (aref a (incf i)) 42)

: Won't (INCF I) be done twice? And isn't there a way around this with
: some more complex SETF machinery? (I don't have "On Lisp" handy right
: now!)

Well, I have "On Lisp" at hand, and here's my suggestion:

(defmacro setf-when-null (place new-val)
  (multiple-value-bind (vars forms var set access)
		       (get-setf-method place)
		       `(let* (,@(mapcar #'list vars forms))
			  (when (null ,access)
				(let ((,(car var) ,new-val))
				  ,set)))))

Cf. P.Graham, On Lisp, section 12.4. (Bugs in the above macro are mine).

					Rolf-Thomas
From: Kelly Murray
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <4rc04t$m70@sparky.franz.com>
In article <············@tribeca.ios.com>, Paul Vianna <·····@tribeca.ios.com> writes:
>> Kelly Murray wrote:
>> > You are confusing the macro expansion evaluation with runtime evaluation.
>> > As a setf location, place does not get evaluated, but is only a reference
>> > to the location.  The macro is what you want, except for this:
>> > ...
>> But what about a case like:
>> 
>> 	(setf-when-null (aref a (incf i)) 42)
>> 
>> Won't (INCF I) be done twice? And isn't there a way around this with
>> some more complex SETF machinery? (I don't have "On Lisp" handy right
>> now!)

You are right, I was confused -- it will evaluate the place subforms twice!

Rolf-Thomas Happes suggestion of using GET-SETF-METHOD is the
right answer:

(defmacro setf-when-null (place new-val)
  (multiple-value-bind (vars forms var set access)
		       (get-setf-method place)
		       `(let* (,@(mapcar #'list vars forms))
			  (when (null ,access)
				(let ((,(car var) ,new-val))
				  ,set)))))
  
-Kelly Murray  ···@franz.com  http://www.franz.com/
From: Kent Pitman
Subject: Re: How to write SETF-WHEN-NULL ?
Date: 
Message-ID: <KMP.96Jul3085653@romulus.harlqn.co.uk>
In article <··········@sparky.franz.com> ···@math.ufl.edu (Kelly Murray) writes:

mmc> (defmacro setf-when-null (place value)
mmc>   `(let ((old-value ,place))
mmc>      (when (null old-value)
mmc>        (setf ,place ,value))))
mmc>
mmc> But here place is evaluated twice.

kem> You are confusing the macro expansion evaluation with runtime evaluation.
kem> As a setf location, place does not get evaluated, but is only a reference
kem> to the location.  The macro is what you want, except for this: [...]

Actually, Kelly, I'm not so sure he's confused.  Maybe he's not
expressing it well.  I think the issue to worry about is the subforms
of place, which in fact CAN get evaluated twice in cases like 
  (setf-when-null (cdr (pop x)) 3)
unless you're careful to arrange for single evaluation of the subforms.
Otherwise, if one is not careful, the expansion has two calls to (pop x).
Certainly it does in the code above.

Mark, it is for precisely this reason that one should not use explicit
calls to GENSYM for writing updater macros but should use what CLTL2
calls GET-SETF-METHOD and GET-SETF-METHOD-MULTIPLE-VALUE, and what
ANSI CL calls GET-SETF-EXPANSION.

You can refer to the text from the ANSI CL specification by going to
the Symbol Index in 

 http://www.harlequin.com/books/HyperSpec/FrontMatter/

and selecting GET-SETF-EXPANSION.  This will show you a definition of the
operator and some sample uses.  But for even more discussion, which I hope
you'll find useful, you should follow the link to section 5.1.1.2 (Setf
Expansions) from the Description section of GET-SETF-EXPANSION.  That section,
5.1.1.2, has lots more info you'll probably want.

Good luck!
 --Kent M Pitman
   ···@harlequin.com