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
> 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 pe...@javamonkey.com
Lisp is the red pill. -- John Fraser, comp.lang.lisp
> 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.
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.
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
> Peter Seibel <pe...@javamonkey.com> wrote in message news:<m34qx7o...@javamonkey.com>...
> > Prof Ric Crabbe <cra...@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.
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.
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?
> until runtime. Maybe (setf (symbol-value ,var) v-set) ?
That's what I *want* to do, but I get
Error: the value of SYMBOL is (SYMBOL-VALUE #:G1287), which is not of type SYMBOL.
[condition type: TYPE-ERROR]
cheers,
ric
> > Peter Seibel <pe...@javamonkey.com> wrote in message news:<m34qx7o...@javamonkey.com>...
> > > Prof Ric Crabbe <cra...@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.
> >
I read the chapter over the weekend. Some of the techniques I knew,
but some were new to me, and I liked the bit in the beginning on how
to approach the problem. I definitely feel better about macros now.
However, I still don't think I see the answer to my problem... The
search continues...
cheers,
ric
> Kent M Pitman <pit...@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:
(defmacro define-counting-variables (n)
(check-type n (integer 0) "a literal integer")
`(progn
,@(loop for i below n
for var = (intern (format nil "*~:@(~R~)*" i))
for fn = (intern (format nil "MY-~:@(~:R~)" i))
append `((defvar ,var ,i)
(defvar ,fn (list) (nth ,var list))))
nil))
(pprint (macroexpand-1 '(define-counting-variables 5)))
(PROGN
(DEFVAR *ZERO* 0)
(DEFVAR MY-ZEROTH (LIST) (NTH *ZERO* LIST))
(DEFVAR *ONE* 1)
(DEFVAR MY-FIRST (LIST) (NTH *ONE* LIST))
(DEFVAR *TWO* 2)
(DEFVAR MY-SECOND (LIST) (NTH *TWO* LIST))
(DEFVAR *THREE* 3)
(DEFVAR MY-THIRD (LIST) (NTH *THREE* LIST))
(DEFVAR *FOUR* 4)
(DEFVAR MY-FOURTH (LIST) (NTH *FOUR* LIST))
NIL)
The point is that at runtime you don't want to iterate, you just want
to execute the result of iterating across a set of forms and
accumulating them.
To do it with a runtime loop, you'd still need some pre-processing at
compile time to avoid really gross runtime work (the kind that makes
you wonder why you compiled your code at all):
(defmacro define-counting-variables-1 (n)
;;; !!! NOT GOOD STYLE - FOR ILLUSTRATION PURPOSE ONLY
(check-type n (integer 0) "a literal integer")
`(dolist (tuple
;; This next loop is done at compile time
',(loop for i below n
for var = (intern (format nil "*~:@(~R~)*" i))
for fn = (intern (format nil "MY-~:@(~:R~)" i))
collect (list i var fn)))
;; This next part (the dolist body) is done at runtime.
(destructuring-bind (i var fn)
(proclaim `(special ,var))
(setf (symbol-value var) i)
(setf (symbol-function fn) #'(lambda (list) (nth i list))))))
(pprint (macroexpand-1 '(define-counting-variables-1 5)))
(DOLIST (TUPLE
'((0 *ZERO* MY-ZEROTH)
(1 *ONE* MY-FIRST)
(2 *TWO* MY-SECOND)
(3 *THREE* MY-THIRD)
(4 *FOUR* MY-FOURTH)))
(DESTRUCTURING-BIND (I VAR FN)
(PROCLAIM `(SPECIAL ,VAR))
(SETF (SYMBOL-VALUE VAR) I)
(SETF (SYMBOL-FUNCTION FN) #'(LAMBDA (LIST) (NTH I LIST)))))
At the point where you don't even do this much preprocessing, you can
do this all by functions:
(defun define-counting-variables-2 (n)
;;; !!! NOT GOOD STYLE - FOR ILLUSTRATION PURPOSE ONLY
;;; ALL THE WORK IS DONE AT RUNTIME
(loop for i below n
for var = (intern (format nil "*~:@(~R~)*" i))
for fn = (intern (format nil "MY-~:@(~:R~)" i))
do (proclaim `(special ,var))
(setf (symbol-value var) i)
(setf (symbol-function fn)
#'(lambda (list) (nth i list)))))
> Prof Ric Crabbe <cra...@crab.cs.usna.edu> writes:
>
> > Kent M Pitman <pit...@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
>
> 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