From: Nicolas M Williams
Subject: Problem with macro argument evaluation
Date: 
Message-ID: <1994Jun20.212625.23563@cs.rit.edu>
Ok, I'm trying to write a with- macro that establishes some context and
then does the body, but I've run into a little problem, perhaps due to
my lack of experience in writing common lisp macros. So, here is the
problem, and thanks in advance to anyone who can [will] help me:

In this code a "menu" is a list whose car is a binding list for let, and
whose cdr is a list of (<selection-text> <function-for this-option>);
do-choice simply calls the function for the choice the user made;
"prompt" comes from the book "On Lisp," and cars is my own concoction
which returns a list of the cars of the elements of its argument. The
problem is that the "let" in with-menu complains that "menu" is not a
list, even though I've made sure that a correctly formed "menu" is
passed to prompt-menu. Help!

(defun do-choice (choice menu)
  (funcall (cadr (elt menu choice))))

(defmacro with-menu (menu &body body)
  `(let ,(car menu) ,@body))

(defun prompt-menu (menu)
  (with-menu (menu)
    (mapcar #'write (cars (cdr menu)))
    (do-choice (prompt "Enter number") menu)))


Again, thanks,

Nick

From: Len Charest
Subject: Re: Problem with macro argument evaluation
Date: 
Message-ID: <2u7ka2$5ij@underdog.jpl.nasa.gov>
In article <······················@cs.rit.edu>,
Nicolas M Williams <·······@cs.rit.edu> wrote:
>
>In this code a "menu" is a list [...]
> The problem is that the "let" in with-menu complains that "menu" is not a
>list, even though I've made sure that a correctly formed "menu" is
>passed to prompt-menu. Help!
>
>(defmacro with-menu (menu &body body)
>  `(let ,(car menu) ,@body))
>
>(defun prompt-menu (menu)
>  (with-menu (menu)
>    (mapcar #'write (cars (cdr menu)))
>    (do-choice (prompt "Enter number") menu)))

MACROEXPAND-1 usually helps when debugging macros. For example,

> (macroexpand-1 
    '(with-menu (menu)
       (mapcar #'write (cars (cdr menu)))
       (do-choice (prompt "Enter number") menu)))

(LET MENU 
  (MAPCAR (FUNCTION WRITE) (CARS (CDR MENU))) 
  (DO-CHOICE (PROMPT "Enter number") MENU))
T

You see that the bindings form of the LET is occupied by the symbol
MENU. This is because WITH-MENU had the list '(MENU) bound to its
formal parameter MENU. The macro then placed the car of this list in
the binding position of LET.

The real problem you are dealing with here is that macros receive
their arguments unevaluated. In other words, the call to WITH-MACRO
never sees the run-time binding of MENU in the PROMPT-MENU function.
Unfortunately, there is no way to create lexical bindings from
quantities computed at run-time (i.e., (CAR MENU) and (CADR MENU)).
The special form PROGV can be used to create *dynamic* bindings, but
then you'll have to use the dreaded EVAL to compute the values for
PROGV from your menu structure. Perhaps you should redesign the menu
data structure or re-evaluate why you want to create these bindings in
the scope of WITH-MENU.

-- 
Len Charest, Jr.				=Speed=of=Lightning==>
JPL Artificial Intelligence Group			      
·······@underdog.jpl.nasa.gov			<<<Power of Thunder>>>
From: Jonathan Kaye
Subject: Re: Problem with macro argument evaluation
Date: 
Message-ID: <2u7i0b$8it@netnews.upenn.edu>
This reply is in two parts: the first part explains the error, and the
second suggests a (so-so) solution-- maybe someone can suggest a
better form.  It uses EVAL, which I'm always hesitant to invoke.

Anyway, I hope it helps.

-jonathan

----------------------------------------------------------------------

Part 1.
-------

In article <······················@cs.rit.edu> you write:
>
>I'm trying to write a with- macro that establishes some context and
>then does the body, but I've run into a little problem
...
>In this code a "menu" is a list whose car is a binding list for let, and
>whose cdr is a list of (<selection-text> <function-for this-option>);
>do-choice simply calls the function for the choice the user made;
...
>The problem is that the "let" in with-menu complains that "menu" is
>not a list, even though I've made sure that a correctly formed "menu"
>is passed to prompt-menu. Help!

LET is a special form which in my implementation (Lucid 4.1) is
implemented as a macro.  You can see this by using MACROEXPAND:

   > (macroexpand '(let ((a 1) (b 2)) (print a)))
   ((LAMBDA (A B) (PRINT A)) 1 2)
   T

This illustrates how "(LET ((VAR1 VAL1) (VAR2 VAL2) ...) BODY)" is
equivalently "( (LAMBDA (VAR1 VAR2) BODY) VAL1 VAL2 )".

So your code (aside from other problems) would generate the following,
as far as I can tell:

   ((LAMBDA MENU (PRINT A)) NIL)

Since an argument list must follow a LAMBDA, Lisp complains that "MENU
is not a list".


Part 2.
-------

Anyway, I don't know how other Lisps handle LET, but my reaction was
to code a solution in the "( (LAMBDA (VAR1 ...) ) ... )" form.

From a given (WITH-MENU (MY-MENU) EXPR1 EXPR2 ...), assuming MY-MENU
has the binding list ((A 1) (B 2)), I build the following form to
evaluate:

(APPLY #'(LAMBDA (MY-MENU A B)
             EXPR1 EXPR2)
        '( (((A 1) (B 2)) ..[this is the value of MY-MENU])
	   1
           2))

Note you have to add a MY-MENU variable because expressions in the
body (like your example) referred to it by name.  Rather than
walking through the body and substituting values directly, I just made
a new parameter so that any references in the body would be correct.

Here are some examples of my so-so solution in action.  Note in
particular the (I assume desired) usefulness of the with-menu form (in
prompt-menu), that I refer to A and B which get their value from
whichever menu is passed in.

> (load "prob")
;;; Loading source file "prob.lisp"
#P"/home/kaye/prob/prob.lisp"
> (prompt-menu *menu1*)

"MENU1, ITEM STRING 0" 
"MENU1, ITEM STRING 1" 
Enter number: 0
IN ITEM 0
A = 1, B = 2
NIL
> (prompt-menu *menu1*)

"MENU1, ITEM STRING 0" 
"MENU1, ITEM STRING 1" 
Enter number: 1
IN ITEM 1
A = 1, B = 2
NIL
> (prompt-menu *menu2*)

"MENU2, ITEM STRING 0" 
"MENU2, ITEM STRING 1" 
Enter number: 0
IN ITEM 0
A = 20, B = 50
NIL
> (prompt-menu *menu2*)

"MENU2, ITEM STRING 0" 
"MENU2, ITEM STRING 1" 
Enter number: 1
IN ITEM 1
A = 20, B = 50
NIL
> 


As far as I could tell, there were errors in the other functions you
gave, so please note the differences.  I didn't bother to change the
names of functions (like "cars", which is misleading as it must do
more than just take the car).  Without further ado, here is my code:

;; make a couple of menus

(setq *menu1* (cons '((a 1)
		      (b 2))
		    (list (list "MENU1, ITEM STRING 0"
				#'(lambda () (format t "IN ITEM 0~%")))
			  (list "MENU1, ITEM STRING 1"
				#'(lambda () (format t "IN ITEM 1~%"))))))

(setq *menu2* (cons '((a 20)
		      (b 50))
		    (list (list "MENU2, ITEM STRING 0"
				#'(lambda () (format t "IN ITEM 0~%")))
			  (list "MENU2, ITEM STRING 1"
				#'(lambda () (format t "IN ITEM 1~%"))))))


(defun do-choice (choice menu)
  (funcall (cadr (elt (cdr menu) choice))))

(defun prompt-menu (menu)
  (with-menu (menu)
    (mapc #'print (cars menu))
    (do-choice (prompt "Enter number: ") menu)
    (format t "A = ~D, B = ~D~%" a b)))


(defmacro with-menu (m &body body)
  `(eval
    (list 'apply
	  (list 'function
		(append
		 (list 'lambda 
		       (cons (quote ,(car m))
			     (mapcar #'car (car ,(car m)))))
		 (quote ,body)))
	  (list 'quote
		(cons ,(car m) (mapcar #'cadr (car ,(car m))))))))


;; Functions not provided in original post ---------------------

(defun prompt (text)
  (let (rval)
    (loop while (not (integerp rval))
	  do
	  (terpri) (princ text)
	  (setq rval (read)))
    rval))

(defun cars (l)
  (mapcar #'car (cdr l)))

*EOF*
From: Nicolas M Williams
Subject: Re: Problem with macro argument evaluation (SUMMARY)
Date: 
Message-ID: <1994Jun21.215232.23490@cs.rit.edu>
Ok, thanks to all those that responded. The problem was due to my not
realizing that the macro expanded at compile time, when the argument
passed to it wasn't bound to any "menu." Two solutions were offered:
using progv instead of let OR using eval. I've settled for a third
solution where I use a macro to build the menus and which returns a
function for prompting the menu it constructs; all bindings lists and
such will be available at compile time to this macro, thus avoiding my
original fault. Thanks again to all who responded. I apologize for the
idiocy of my mistake, I'm just learning to write lisp macros.

Nick

PS: using progv worked, as did the solution using eval.
From: Lou Steinberg
Subject: Re: Problem with macro argument evaluation (SUMMARY)
Date: 
Message-ID: <LOU.94Jun23233401@atanasoff.rutgers.edu>
I'm probably taking this more seriously than you meant it, but I just
can't let it pass without a comment:

   I apologize for the idiocy of my mistake, I'm just learning to write 
                       ^^^^^^
   lisp macros.

Please don't be so hard on yourself.  For some reason many people find
it very hard to internalize the idea that macros are expanded at
compile time, and often fail to notice some fairly obvious
consequences of this fact.  (Maybe this is caused by the interactive
nature of lisp environments - you tend to forget there is such a thing
as compile time??  I dunno.)  You seem to have caught on pretty
quickly once it was explained to you, which is more than a lot of
people do.  
--
					Lou Steinberg

uucp:   {pretty much any major site}!rutgers!aramis.rutgers.edu!lou 
internet:   ···@cs.rutgers.edu
From: Nicolas M Williams
Subject: Re: Problem with macro argument evaluation (SUMMARY)
Date: 
Message-ID: <1994Jun24.051834.1445@cs.rit.edu>
In article <······················@cs.rit.edu> ·······@cs.rit.edu (me) writes:

>Nick

>PS: using progv worked, as did the solution using eval.

And here is what the solution that I settled on looks like:

(defun do-choice (choice menu)
  (let ((call (cdr (elt menu choice))))
       (apply (symbol-function (car call )) (cdr call))))

(defmacro make-prompt-function2 (name binds1 binds2 menu-list)
  `(let ,binds1
       (defun ,name ()
	      (let ,binds2
		   (mapcar #'princ (cars ,menu-list))
		   (do-choice (prompt "Enter number: ") ,menu-list)))))

(make-prompt-function2 test-my-macro () ((a 3) (b 4)) `(("1. a
" princ ,a) ("2. b
" princ ,b)))

Again, cars is my own concoction that simply returns a list of the car's
of the elements of its argument. The prompt function is the one that
appears in "On Lisp." Note that though this works it still needs some
work, most notably I will pass to the make- macro the functions used to
print the menu choices and to prompt for input, thus allowing one to use
any desired interface; or perhaps I'll make those functions arguments to
the function returned by the make- macro. I will also put a loop in the
function returned by the make macro, but that'll have to wait until I
come up with a way of exiting (no sweat). The fact that the menu
prompting function returned by the make- macro will loop should make it
clear why I wanted to be able to establish those bindings.

Nick
From: Peter Dudey
Subject: An elegant solution? (was Re: Problem with macro argument evaluation)
Date: 
Message-ID: <DUDEYP.94Jun21151311@hume.cs.orst.edu>
In article <······················@cs.rit.edu> ·······@cs.rit.edu (Nicolas M Williams) writes:

   Newsgroups: comp.lang.lisp
   From: ·······@cs.rit.edu (Nicolas M Williams)
   Date: Mon, 20 Jun 1994 21:26:25 GMT

   In this code a "menu" is a list whose car is a binding list for let, and
   whose cdr is a list of (<selection-text> <function-for this-option>);
   do-choice simply calls the function for the choice the user made;
   "prompt" comes from the book "On Lisp," and cars is my own concoction
   which returns a list of the cars of the elements of its argument. The
   problem is that the "let" in with-menu complains that "menu" is not a
   list, even though I've made sure that a correctly formed "menu" is
   passed to prompt-menu. Help!

   (defun do-choice (choice menu)
     (funcall (cadr (elt menu choice))))

   (defmacro with-menu (menu &body body)
     `(let ,(car menu) ,@body))

I'm enjoying _On_Lisp_ right now, too, so I just had to dig in to this
one.  Your macros looked like they would work, but I tried them, and
no such luck.  After much mucking about, and reading the other
response, I finally came up with this:

(defmacro with-menu (menu &body body)
`(let ,(car (symbol-value menu)) ,@body))

This seems to do the trick.  The call to symbol-value seems necessary;
Graham says, on page 139, "macroexpansion only has access to forms,
not to their values."  I thought a function might be the way to go,
but I couldn't get that to work...

-- 
; Peter Dudey, MS student in Artificial Intelligence, Oregon State University ;
; ······@research.cs.orst.edu | hagbard on IGS | 257 NE 13th, Salem, OR 97301 ;
;									      ;
;       NOTICE:  As of August 20, 1994, I will become Peter Dudey Drake.      ;