From: Prof Ric Crabbe
Subject: macro help?
Date: 
Message-ID: <wzcznezdcr8.fsf@crab.cs.usna.edu>
I'm afraid I'm not very good with macros and I've been banging my head
against this problem all day.  

I used to have a silly little macro used for defining "attributes" in
a decision tree.  An attribute was essentially a function and a list
of numeric ranges, both with the same name:

(defmacro define-attribute (attribute values arg-list &body body)
  `(progn 
     (push ,attribute *attributes*)
     (defvar ,attribute)
     (setf ,attribute ',values)
     (defun ,attribute ,arg-list
       ,@body)))

This might be called with:

(define-attribute dx-by-5 (5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
			   105 110 115 120 125 130 135 140 145) (x)
  (let ((v (dx x)))
    (do ((x 5 (+ x 5)))
	((or (>= x v) (> x 145)) x))))

I have 2 problems with this.  The first is that the list of numbers
could have been automatically generated.  The second is that I end up
defining lots of nearly identical attributes that could also have been
generated automatically, e.g.:

(define-attribute dx-by-10 (10 20 30 40 50 60 70 80 90 100
			   110 120 130 140 150) (x)
  (let ((v (dx x)))
    (do ((x 10 (+ x 10)))
	((or (>= x v) (> x 150)) x))))


So I decided to write a macro that would generate these attributes
automatically, so that  

(define-attributes dx ((5 5 145) ; high 145, low 5, and step size 5
                       (10 10 150)) (x)
   (let ((v (dx x)))
    (do ((x 10 (+ x 10)))
	((or (>= x v) (> x 150)) x))))

would generate the 2 previous attributes.  Anyway, I'm stuck.  I
initially decided to worry about the multiple functions and leave the
generation of the ranges for later.  My first complete try came out
like:

(defmacro define-attributes (attribute values arg-list &body body)
  `(dolist (v-set ',values)
    (define-attribute ,attribute v-set ,arg-list ,@body)))

(defmacro define-attribute (attribute v-set arg-list &body body)
  ;;attribute is name, values is a list of lists.
  ;; each sublist is the step, the high, and the low for the range.
  ;; the rest just define the function.
  (let ((attribute-fullname 
         (intern (format t "~a-by-~A" (symbol-name attribute) (car v-set)))))
    `(progn                                                   ^^^^^^^^^^^ 
       (push ,attribute-fullname *attributes*)
       (defvar ,attribute-fullname ',v-set)
       (setf ,attribute-fullname ',v-set)
       (defun ,attribute-fullname ,arg-list
	 ,@body))))


But I now understand why that car won't work.

So I tried:
(defmacro define-attribute (attribute values arg-list &body body)
  `(dolist (v-set ',values)
     (push (intern 
            (format nil "~a-by-~A" (symbol-name ',attribute) (car v-set))) *attributes*)
     (defvar (intern 
              (format nil "~a-by-~A" 
                      (symbol-name ',attribute) (car v-set))))
     (setf (intern 
            (format nil "~a-by-~A" 
                    (symbol-name ',attribute) (car v-set))) ',v-set)
     (defun (intern 
             (format nil "~a-by-~A" 
                     (symbol-name ',attribute) (car v-set))) ,arg-list ,@body)))

but I can't do that either cause I need real symbols instead of the (intern)s...

next...

(defmacro define-attribute (attribute values arg-list &body body)
  (let ((var (gensym)))
    `(dolist (v-set ',values)
       (let ((,var (intern (format nil "~a-by-~A" (symbol-name ',attribute) (car v-set)))))
	 (push ,var *attributes*)
	 (defvar ,var v-set)
	 (setf ,var v-set)
	 (defun ,var ,arg-list
	   ,@body)))))


now the setf is assigning to the gensym... *sigh* Clearly, i have no
idea what I'm doing.  Can anyone enlighten me on the proper way to do
this? (Also, I suspect the interning of the string might be considered
bad style.  What should that be?)

thanks,
ric

From: Peter Seibel
Subject: Re: macro help?
Date: 
Message-ID: <m34qx7ohna.fsf@javamonkey.com>
Prof Ric Crabbe <······@crab.cs.usna.edu> writes:

> I'm afraid I'm not very good with macros and I've been banging my
> head against this problem all day.

[snip]

> now the setf is assigning to the gensym... *sigh* Clearly, i have no
> idea what I'm doing. Can anyone enlighten me on the proper way to do
> this? (Also, I suspect the interning of the string might be
> considered bad style. What should that be?)

This might be a good time for me to point out that I've put some more
chapters of my upcoming Common Lisp book up on the web at 

  <http://www.gigamonkeys.com/book/>

In particular you might want to check out the chapter on writing
macros at:

  <http://www.gigamonkeys.com/book/macros-defining-our-own.html>

Since you're right in the thick of trying to understand macros, I'd
love to hear your feedback on whether this chapter helps at all or
just adds to your confusion.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Justin Dubs
Subject: Re: macro help?
Date: 
Message-ID: <2e262238.0311140946.68849c8f@posting.google.com>
Peter Seibel <·····@javamonkey.com> wrote in message news:<··············@javamonkey.com>...
> Prof Ric Crabbe <······@crab.cs.usna.edu> writes:
> 
> > I'm afraid I'm not very good with macros and I've been banging my
> > head against this problem all day.
> 
> [snip]
> 
> > now the setf is assigning to the gensym... *sigh* Clearly, i have no
> > idea what I'm doing. Can anyone enlighten me on the proper way to do
> > this? (Also, I suspect the interning of the string might be
> > considered bad style. What should that be?)
> 
> This might be a good time for me to point out that I've put some more
> chapters of my upcoming Common Lisp book up on the web at 
> 
>   <http://www.gigamonkeys.com/book/>
> 
> In particular you might want to check out the chapter on writing
> macros at:
> 
>   <http://www.gigamonkeys.com/book/macros-defining-our-own.html>
> 
> Since you're right in the thick of trying to understand macros, I'd
> love to hear your feedback on whether this chapter helps at all or
> just adds to your confusion.

Nice work on the new chapters.  I found a few typos I thought I should point out:

macros-defining-our-own.html:

duplicate word:
"Principle of Least Astonishment when when implementing macros."

s/with/without/:
"In future chapters ... that would be virtually impossible with macros."

Justin Dubs
From: Peter Seibel
Subject: Re: macro help?
Date: 
Message-ID: <m3llqin8fb.fsf@javamonkey.com>
······@eos.ncsu.edu (Justin Dubs) writes:

> Peter Seibel <·····@javamonkey.com> wrote in message news:<··············@javamonkey.com>...
> > Prof Ric Crabbe <······@crab.cs.usna.edu> writes:
> > 
> > > I'm afraid I'm not very good with macros and I've been banging my
> > > head against this problem all day.
> > 
> > [snip]
> > 
> > > now the setf is assigning to the gensym... *sigh* Clearly, i have no
> > > idea what I'm doing. Can anyone enlighten me on the proper way to do
> > > this? (Also, I suspect the interning of the string might be
> > > considered bad style. What should that be?)
> > 
> > This might be a good time for me to point out that I've put some more
> > chapters of my upcoming Common Lisp book up on the web at 
> > 
> >   <http://www.gigamonkeys.com/book/>
> > 
> > In particular you might want to check out the chapter on writing
> > macros at:
> > 
> >   <http://www.gigamonkeys.com/book/macros-defining-our-own.html>
> > 
> > Since you're right in the thick of trying to understand macros, I'd
> > love to hear your feedback on whether this chapter helps at all or
> > just adds to your confusion.
> 
> Nice work on the new chapters.  I found a few typos I thought I should point out:

Thanks.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Kent M Pitman
Subject: Re: macro help?
Date: 
Message-ID: <sfw4qx7dki4.fsf@shell01.TheWorld.com>
Prof Ric Crabbe <······@crab.cs.usna.edu> writes:

> I'm afraid I'm not very good with macros and I've been banging my head
> against this problem all day.  
> 
> I used to have a silly little macro used for defining "attributes" in
> a decision tree.  An attribute was essentially a function and a list
> of numeric ranges, both with the same name:
> 
> (defmacro define-attribute (attribute values arg-list &body body)
>   `(progn 
>      (push ,attribute *attributes*)
>      (defvar ,attribute)
>      (setf ,attribute ',values)

Instead of the previous two lines, use

 (defparameter ,attribute ',values)

There is no difference between defvar and defparameter other than that
1-argument defvar doesn't assign the variable and the 2-argument one
assigns it only if unbound, but since you unconditionally assign it as
the very next thing, you should be using defparameter, which does just
that.

>      (defun ,attribute ,arg-list
>        ,@body)))
> 
> This might be called with:
> 
> (define-attribute dx-by-5 (5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
> 			   105 110 115 120 125 130 135 140 145) (x)
>   (let ((v (dx x)))
>     (do ((x 5 (+ x 5)))
> 	((or (>= x v) (> x 145)) x))))
> 
> I have 2 problems with this.  The first is that the list of numbers
> could have been automatically generated.  The second is that I end up
> defining lots of nearly identical attributes that could also have been
> generated automatically, e.g.:
> 
> (define-attribute dx-by-10 (10 20 30 40 50 60 70 80 90 100
> 			   110 120 130 140 150) (x)
>   (let ((v (dx x)))
>     (do ((x 10 (+ x 10)))
> 	((or (>= x v) (> x 150)) x))))
> 
> 
> So I decided to write a macro that would generate these attributes
> automatically, so that  
> 
> (define-attributes dx ((5 5 145) ; high 145, low 5, and step size 5
>                        (10 10 150)) (x)
>    (let ((v (dx x)))
>     (do ((x 10 (+ x 10)))
> 	((or (>= x v) (> x 150)) x))))
> 
> would generate the 2 previous attributes.  Anyway, I'm stuck.  I
> initially decided to worry about the multiple functions and leave the
> generation of the ranges for later.  My first complete try came out
> like:
> 
> (defmacro define-attributes (attribute values arg-list &body body)
>   `(dolist (v-set ',values)
>     (define-attribute ,attribute v-set ,arg-list ,@body)))
> 
> (defmacro define-attribute (attribute v-set arg-list &body body)
>   ;;attribute is name, values is a list of lists.
>   ;; each sublist is the step, the high, and the low for the range.
>   ;; the rest just define the function.
>   (let ((attribute-fullname 
>          (intern (format t "~a-by-~A" (symbol-name attribute) (car v-set)))))
>     `(progn                                                   ^^^^^^^^^^^ 
>        (push ,attribute-fullname *attributes*)
>        (defvar ,attribute-fullname ',v-set)
>        (setf ,attribute-fullname ',v-set)
>        (defun ,attribute-fullname ,arg-list
> 	 ,@body))))
> 
> 
> But I now understand why that car won't work.
> 
> So I tried:
> (defmacro define-attribute (attribute values arg-list &body body)
>   `(dolist (v-set ',values)
>      (push (intern 
>             (format nil "~a-by-~A" (symbol-name ',attribute) (car v-set))) *attributes*)
>      (defvar (intern 
>               (format nil "~a-by-~A" 
>                       (symbol-name ',attribute) (car v-set))))
>      (setf (intern 
>             (format nil "~a-by-~A" 
>                     (symbol-name ',attribute) (car v-set))) ',v-set)
>      (defun (intern 
>              (format nil "~a-by-~A" 
>                      (symbol-name ',attribute) (car v-set))) ,arg-list ,@body)))
> 
> but I can't do that either cause I need real symbols instead of the (intern)s...
> 
> next...
> 
> (defmacro define-attribute (attribute values arg-list &body body)
>   (let ((var (gensym)))
>     `(dolist (v-set ',values)
>        (let ((,var (intern (format nil "~a-by-~A" (symbol-name ',attribute) (car v-set)))))
> 	 (push ,var *attributes*)
> 	 (defvar ,var v-set)

DEFVAR is a declarative form.  It doesn't make sense to do in a loop.

Maybe you want (proclaim '(special ,var))
Assigning it here is just overridden by this next, sort of.

> 	 (setf ,var v-set)

SETF needs to know the var name at compile time, but you won't know it 
until runtime. Maybe (setf (symbol-value ,var) v-set) ?

> 	 (defun ,var ,arg-list
> 	   ,@body)))))
> 
> 
> now the setf is assigning to the gensym... *sigh* Clearly, i have no
> idea what I'm doing.  Can anyone enlighten me on the proper way to do
> this? (Also, I suspect the interning of the string might be considered
> bad style.  What should that be?)

As long as the interning is done at compile time, it's ok.
(Yours is not.  Maybe you could bring the loop back into compile time?)

There are probably other problems.  I named just a few.
From: Prof Ric Crabbe
Subject: Re: macro help?
Date: 
Message-ID: <wzcllqecz1i.fsf@crab.cs.usna.edu>
Kent M Pitman <······@nhplace.com> writes:

> Prof Ric Crabbe <······@crab.cs.usna.edu> writes:
> 
> > Kent M Pitman <······@world.std.com> writes:
> > > 
> > > DEFVAR is a declarative form.  It doesn't make sense to do in a loop.
> > 
> > then what I was trying to do wasn't clear.  The point is that each
> > pass through, the variable being defined would have a different name.
> [...]
> > Can this be done without a loop?
> 
> Not the right question.  The loop needs to be there BUT at compile time
> not at runtime.  Consider:
> 

Ah.  Now I am enlightened, and I now understand that you tried to tell
me this before, but I didn't get it.  Thank you for your patience;
I've learned more about macros this week than in all my other years of
lisp programming.

cheers,
ric
From: Thomas A. Russ
Subject: Re: macro help?
Date: 
Message-ID: <ymiekw5v77a.fsf@sevak.isi.edu>
Prof Ric Crabbe <······@crab.cs.usna.edu> writes:

> 
> This is what I want to write:
> 
> (define-attribute dx ((5 10 100) (10 20 100)) (dx x))
> 
> and this is what I want generated:
> 
> (defparamter dx-by-5 '(10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100))
> (defun dx-by-5 (x)
>   (let ((v (dx x)))  ;;this is boilerplate, except (dx x)
>     (do ((x 10 (+ x 5)))
> 	((or (>= x v) (> x 100)) x))))
> 
> (defparamter dx-by-10 '(20 30 40 50 60 70 80 90 100))
> (defun dx-by-10 (x)
>   (let ((v (dx x)))
>     (do ((x 10 (+ x 5)))
> 	((or (>= x v) (> x 100)) x))))
> 
> etc., where the number and contents of the functions created is
> determined by the number of sublists in the second argument and the
> contents of the third argument.
> 
> Can this be done without a loop?

OK.  This should not be too difficult.  You may want to change some
of the names of the variables to better reflect the domain.  The real
problem is the dependence on the variable "X" as the function parameter.
(Not to mention the potential confusion between the two X variables)
It also isn't clear to me what the defined parameter is used for.

(defmacro define-attribute (attribute-name value-lists target-value)
  `(progn
     ,@(loop for (increment start end) in value-lists
             as name = (intern (format nil "~A-BY-~A" attribute-name increment))
             collect `(defparameter ,name 
	                            ',(loop for i from start to end by increment
					    collect i))
             collect `(defun ,name (x)   ;; ! Must be X !!!!
                        (let ((v ,target-value))
                          (do ((x ,start (+ x ,increment)))
                              ((or (>= x v) (> x ,end)) x)))))))

(macroexpand '(define-attribute dx ((5 10 100) (10 20 100)) (dx x)))

  ==>

(PROGN (DEFPARAMETER DX-BY-5
           (QUOTE (10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100)))
       (DEFUN DX-BY-5 (X)
         (LET ((V (DX X))) (DO ((X 10 (+ X 5))) ((OR (>= X V) (> X 100)) X) )))
       (DEFPARAMETER DX-BY-10 (QUOTE (20 30 40 50 60 70 80 90 100)))
       (DEFUN DX-BY-10 (X)
         (LET ((V (DX X))) (DO ((X 20 (+ X 10))) ((OR (>= X V) (> X 100)) X) ))))


Notes:
1.  One should introduce some gensyms for variables in the generated
code for the function definitions, but I omitted that from this version
to make the macro a bit simpler.  Doing it right adds a let in the
macro code with some gensyms which are then substituted into the
generated code.  See below.

2.  One might also need to worry a bit about the case of the names being
generated when making the namestring passed to intern.

3.  It is generally bad to have a fixed variable that is required to be
bound when substituting values in to the function.  This can be handled
by perhaps one of the following means:
  a)  Specify the variable in the macro call.  Needs an extra variable
      argument.
  b)  Specify a function and have it funcalled:
         (define-attribute dx ((5 10 100) (10 20 100)) #'dx)
      and have the value binding be something like:
        (defun dx-by-10 (y)
           (let ((v (funcall #'dx y)))
             ...))


(defmacro define-attribute (attribute-name value-lists target-function)
  (let ((function-var (gensym "ARG-"))
        (target-var (gensym "TARGET-"))
        (loop-var (gensym "LOOP-")))
    `(progn
       ,@(loop for (increment start end) in value-lists
            as name = (intern (format nil "~A-BY-~A" attribute-name increment))
            collect `(defparameter ,name 
	                              ',(loop for i from start to end by increment
		 			    collect i))
            collect `(defun ,name (,function-var)
                        (let ((,target-var (funcall ,target-function ,function-var)))
                          (do ((,loop-var ,start (+ ,loop-var ,increment)))
                              ((or (>= ,loop-var ,target-var)
                                   (> ,loop-var ,end)) ,loop-var))))))))



-- 
Thomas A. Russ,  USC/Information Sciences Institute