From: Barry Perryman
Subject: Help with macros
Date: 
Message-ID: <879935336.2050@dejanews.com>
Hi Everyone,

I'm stuck on a macros exercise from Graham's ACL. The question asks
you what's wrong with this definition of push:

(defmacro duff-push (obj lst)   ; called duff-push by me
  `(setf ,lst (cons ,obj ,lst)))

I don't seem to be able to find any difference in use between this and
supplied push in clisp and freelisp. Can anyone give me some clues while
I still have some sanity left.

Thanks

Barry

-------------------==== Posted via Deja News ====-----------------------
      http://www.dejanews.com/     Search, Read, Post to Usenet

From: Aaron Gross
Subject: Re: Help with macros
Date: 
Message-ID: <lyoh3hx9z5.fsf@cruella.bfr.co.il>
Barry Perryman <·········@Computasoft.com> writes:

> I'm stuck on a macros exercise from Graham's ACL. The question asks
> you what's wrong with this definition of push:
> 
> (defmacro duff-push (obj lst)   ; called duff-push by me
>   `(setf ,lst (cons ,obj ,lst)))

Try evaluating this form:

(push 'a (cdr (list (print "evaluated once") 'b)))

Now try it again with PUSH replaced by DUFF-PUSH.

Also, for future reference, the Common Lisp functions MACROEXPAND and
MACROEXPAND-1 are invaluable for figuring out what's going on with
macros.

OK, now it's my turn to ask a question about PUSH. I was going to give
an example showing that DUFF-PUSH evaluated its arguments in the wrong
order, e.g.,

(push (progn (print "first") 'a) 
      (cdr (list (print "second") 'b)))

I was surprised that in Allegro Common Lisp, the result (using PUSH,
not DUFF-PUSH) was

"second" 
"first" 
(A B)

Is this right? The HyperSpec for PUSH says:

   The effect of (push item place) is equivalent to
 
 (setf place (cons item place))
 
   except that the subforms of place are evaluated only once, and item is
   evaluated before place.

Is this a bug in ACL, or am I missing something really obvious?

-- 
Aren't you glad you use RFC-1036?
Don't you wish everyone did?
From: Erik Naggum
Subject: Re: Help with macros
Date: 
Message-ID: <3088952859018297@naggum.no>
* Aaron Gross
| Also, for future reference, the Common Lisp functions MACROEXPAND and
| MACROEXPAND-1 are invaluable for figuring out what's going on with
| macros.

(quoted only to maintain context.)

| OK, now it's my turn to ask a question about PUSH. I was going to give
| an example showing that DUFF-PUSH evaluated its arguments in the wrong
| order, e.g.,
| 
| (push (progn (print "first") 'a) 
|       (cdr (list (print "second") 'b)))
:
| Is this a bug in ACL, or am I missing something really obvious?

well, try and macroexpand the form.  :)

when I first got ACL 4.3, I spent a *lot* of time with Appendix A, "Allegro
CL and the Common Lisp Standard".  it was _most_ educational.  see item 48
on page A-5 for their story on PUSH-EVALUATION-ORDER:ITEM-FIRST, the full
text at http://www.harlequin.com/books/HyperSpec/Issues/iss279-writeup.html

sadly, some of the items are out of date: ACL _does_ conform to a lot of
stuff where Appendix A says it doesn't.  I can live with this.

#\Erik
-- 
if you think this year is "97", _you_ are not "year 2000 compliant".

see http://sourcery.naggum.no/emacs/ for GNU Emacs 20-related material.
From: Kent M Pitman
Subject: Re: Help with macros
Date: 
Message-ID: <sfw3ektexis.fsf@world.std.com>
Barry Perryman <·········@Computasoft.com> writes:

> [from Graham's ACL] what's wrong with this definition of push:
> 
> (defmacro duff-push (obj lst)   ; called duff-push by me
>   `(setf ,lst (cons ,obj ,lst)))

(flet ((abc (x y z)
	 (list (list x) (list y) (list z))))
  (list
    (let ((foo (abc 'a1 'b1 'c1)) (bar 0))
      (push 'test-1 (nth (incf bar) foo))
      (list foo bar))
    (let ((foo (abc 'a2 'b2 'c2)) (bar 0))
      (duff-push 'test-2 (nth (incf bar) foo))
      (list foo bar))))

=> ( ( ((A1) (TEST-1 B1) (C1)) 1 ) 
     ( ((A2) (TEST-2 C2) (C2)) 2 ))

Does this help?
From: Matthias Hoelzl (tc)
Subject: Re: Help with macros
Date: 
Message-ID: <87vhxov4ay.fsf@gauss.muc.de>
Barry Perryman <·········@Computasoft.com> writes:

> I'm stuck on a macros exercise from Graham's ACL. The question asks
> you what's wrong with this definition of push:
> 
> (defmacro duff-push (obj lst)   ; called duff-push by me
>   `(setf ,lst (cons ,obj ,lst)))

The problems are that you are
 a) evaluating `lst' and `obj' in the wrong order and
 b) evaluating `lst' twice.

This is no problem in the most common case where `lst' is a symbol,
but since CL allows more complex expressions for "places" than simple
symbols you have to be careful. For example the following form

(let ((lst (list 1 2 3)))
  (setf (car lst) 'a)
  lst)

will evaluate to (a 2 3).  Similarly you can do

(let ((lst (list (list 1) 2 3)))
  (push 'a (car lst))
  lst)

and obtain the list ((a 1) 2 3).  Any non empty list can be used as a
place, provided that the operator (the car of the list) has a so
called "setf expander" defined; the standard requires that expressions
of the form `(c[ad]+r ...)' can be used as places.  But this means
that 

(let ((lst (list '())))
   (duff-push (progn (princ "1") 'a)
	      (car (progn (princ "2") lst)))
   (princ lst))

is a valid form as well.

This example will print 212 as first output, and not 12 as it should,
see the Hyper Spec (esp. chapter 5 and macro `push') for details about
the behavior of `push'.  (CMUCL seems to get the order of evaluations
wrong for the built in `push' as well.)

It seems at first glance that a solution for these problems would be
to evaluate `obj' and `lst' once in a `let' form, i.e.,

(defmacro duff-push-1 (obj lst)
  (let ((gobj (gensym))
	(glst (gensym)))
    `(let ((,gobj ,obj)
	   (,glst ,lst))
       (setf ,glst (cons ,gobj ,glst)))))

but oops, we now end up changing the binding of `glst' and not of the
original list.

Thus we need a more elaborate solution.  Fortunately Common Lisp
offers support for this problem in the form of the function
`get-setf-expansion'.  Here is an explanation what Graham's
implementation of `push' from page 301 does:

(defmacro duff-push-2 (obj place)
  (multiple-value-bind (vars forms var set access)
      (get-setf-expansion place)
    (let ((g (gensym)))
      `(let* ((,g ,obj)
              ,@(mapcar #'list vars forms)
              (,(car var) (cons ,g ,access)))
         ,set))))

The `multiple-value-bind' binds the variables `vars' to the values
that are returned from `get-setf-expansion', ..., that is:

 `vars'   <= a list (v1 v2 ...) of unique symbols,
 `forms'  <= a list (f1 f2 ... fn) of forms, such that each element 
             of `vars' should be bound to the value of the
             corresponding element of `forms' in a `let*' form,
 `var'    <= a list of symbols naming temporary locations that
             should be bound to the values that will be assigned,
 `set'    <= a form that can reference the variables in  `vars' and
             in `var' and that changes the value of `place',
 `access' <= a form that can access the variables in `vars' and 
             returns the value of `place'.

Here is an example for `get-setf-expansion':

(multiple-value-bind (vars forms var set access)
    (get-setf-expansion 'z)
  (list vars forms var set access))

results in the list

(nil nil (#:<gensym 1>) (setq z #:<gensym 1>) z)

`Vars' and `Forms' are empty in this case, and if you bind #:<gensym 1> 
(the car of `var') to the new value of `z' the form bound to `set'
will update `z'; the form bound to `access' will return the value of
`z'.

A slightly more complicated example:

(multiple-value-bind (vars forms var set access)
    (get-setf-expansion '(car z))
  (list vars forms var set access))

returns 

((#:<gensym 1>) 
 (z) 
 (#:<gensym 2>) 
 (common-lisp::%rplaca #:<gensym 1> #:<gensym 2>) 
 (car #:<gensym 1>))

in CMUCL (with a different notation for gensyms).  This time `vars' is
`(#:<gensym 1>)' and `forms' is `(z)'.  If we bind the new value of
`(car z)' to `#:<gensym 2>' then again the form bound to `set' will
update `(car z)' and the form bound to `access' will return the value
of `(car z)'.

Ok, back to duff-push-2.  As in `duff-push-1' we evaluate `obj' once
and bind the result to a unique name; the form (mapcar #'list vars
forms) generates a list of the form ((v1 f1) (v2 f2) ...), which is
spliced in the `let*'-form, and finally we bind the first variable in
`var' to the new value of the list, so that the macro-expansion of
(duff-push-2 'a z) looks like

(let* (;; The first binding is from (,g ,obj)
       ;;
       (#<gensym 1> 'a)
       ;;                     
       ;; `Vars' and `forms' are empty, so no binding is generated
       ;; by ,@(mapcar #'list vars forms) in this example.
       ;;
       ;; This is (,(car var) (cons ,g ,access))
       ;;
       (#<gensym 2> (cons #<gensym 1> z)))  
  (setq z #<gensym 2>))

which is of course equivalent to (setq z (cons 'a z)).  If you expand
some more complex examples the use of the `vars' and `forms' lists
will become clear:

(macroexpand-1
 '(duff-push-2 (progn (princ "1") 1) 
	       (car (progn (princ "2") lst))))

returns 

(LET* ((#:G9 (PROGN (PRINC "1 ") 1))   ; (,g ,obj)
       (#:G8 (PROGN (PRINC "2 ") LST)) ; ,@(mapcar #'list vars forms)
       (#:G7 (CONS #:G9 (CAR #:G8))))  ; (,(car var) (cons ,g ,access))
  (COMMON-LISP::%RPLACA #:G8 #:G7))
T


Hope this helps.

  Matthias