Reputation: 19940
I want to assign multiple constants within one macro call. But the code below only assigns the last constant, the constants which where defined before are not available.
; notes.lisp
(defconstant N_oct0 0)
(defmacro N_defheight(_oct _note _offset)
`(defconstant ,(read-from-string (concatenate 'string _note _oct))
,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
_offset)))
(defmacro N_octave(_octave)
`(N_defheight ,_octave "c" 0)
`(N_defheight ,_octave "c#" 1)
`(N_defheight ,_octave "des" 1)
`(N_defheight ,_octave "d" 2)
`(N_defheight ,_octave "d#" 3)
`(N_defheight ,_octave "es" 3)
`(N_defheight ,_octave "e" 4)
`(N_defheight ,_octave "f" 5)
`(N_defheight ,_octave "f#" 6)
`(N_defheight ,_octave "ges" 6)
`(N_defheight ,_octave "g" 7)
`(N_defheight ,_octave "g#" 8)
`(N_defheight ,_octave "as" 8)
`(N_defheight ,_octave "a" 9)
`(N_defheight ,_octave "a#" 10)
`(N_defheight ,_octave "b" 10)
`(N_defheight ,_octave "h" 11))
(N_octave "0")
After loading the file in sbcl, I have only the h0 constant, but none of the c0..b0 constants.
$ sbcl
This is SBCL 1.0.40.0.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "notes")
T
* h0
11
* c0
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD
"initial thread" RUNNING
{1002C34141}>:
The variable C0 is unbound.
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-INT:SIMPLE-EVAL-IN-LEXENV C0 #<NULL-LEXENV>)
0]
So how can I change the macro to execute all defconstant
calls, not only the last one?
Upvotes: 4
Views: 385
Reputation: 29011
Several statements are not concatenated this way in Lisp. Try to use the progn
construct:
(defmacro N_octave(_octave)
`(progn (N_defheight ,_octave "c" 0)
(N_defheight ,_octave "c#" 1)
... ))
Upvotes: 2
Reputation: 139251
Other answers already pointed out the correct solution: to use PROGN.
Here some remarks about 'style':
(defmacro N_defheight(_oct _note _offset)
`(defconstant ,(read-from-string (concatenate 'string _note _oct))
,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
_offset)))
example:
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
...
(N_defheight ,_octave "h" 11))
Above could be simplified with a simple iteration:
`(progn
,@(loop for (note offset) in '(("c" 0) ("c#" 1) ... ("h" 11))
collect (list 'defheight octave note offset)))
or using MAPCAR
`(progn
,@(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
'(("c" 0) ("c#" 1) ... ("h" 11))))
The effect is less typing and the important symbols are written only once. One has to decide what is better: many similar looking statements or a small program transforming a data description.
But there is another problem: the data is coded into the macro.
This is wrong. A macro should do the code transformation and not contain data. Again, you can do everything, but good Lisp requires to have some feeling for programming style. I would put the notes and offsets as a list into a variable and use that in the macro, or provide it as a parameter:
(defvar *notes-and-offsets*
'(("c" 0) ("c#" 1) ... ("h" 11)))
(defoctave (octave notes-and-offsets)
`(progn
,@(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
(eval notes-and-offsets))))
(defoctave "0" *notes-and-offsets*)
Now there is another problem. We define constants with names like C0
. A constant in Lisp always refers to the global constant value. Rebinding is not allowed. That means that C0 is no longer a valid local variable name in your program. If you know that you will never use C0 as a variable name, that's fine - but this problem may not be known later during maintenance. For this reason, it is good style to put plus signs around names of constants like this: +C0+
. Again, just a convention. You can also use your own specialized naming convention, which should not clash with your names for variables. like NOTE-C0
.
If your intention is to always use an identifier like c0
as a global name for a constant note value, then you don't have a problem - you just need to understand that then with DEFCONSTANT, you can't use c0
no longer as a variable. It might be a good idea to have your own package, then.
Next: when you want to use variables when computing a macro expansion, then you need to make sure that the variables have values. Either load a file before or use EVAL-WHEN.
This leads to this code:
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *n-oct0* 0)
(defvar *notes-and-offsets*
'((c 0) (c# 1) (des 1) (d 2)
(d# 3) (es 3) (e 4) (f 5)
(f# 6) (ges 6) (g 7) (g# 8)
(as 8) (a 9) (a# 10) (b 10)
(h 11)))
) ; end of EVAL-WHEN
(defmacro defheight (oct note offset)
`(defconstant ,(intern (format nil "~a~a" note oct))
(+ ,(intern (format nil "*N-OCT~a*" oct))
,offset)))
(defmacro defoctave (octave notes-and-offsets)
`(progn
,@(mapcar (lambda (note offset)
(list 'defheight octave note offset))
(mapcar #'first (eval notes-and-offsets))
(mapcar #'second (eval notes-and-offsets)))))
(defoctave 0 *notes-and-offsets*)
Upvotes: 5
Reputation: 114461
You need to expand to a progn
form
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
(N_defheight ,_octave "c#" 1)
(N_defheight ,_octave "des" 1)
(N_defheight ,_octave "d" 2)
(N_defheight ,_octave "d#" 3)
(N_defheight ,_octave "es" 3)
(N_defheight ,_octave "e" 4)
(N_defheight ,_octave "f" 5)
(N_defheight ,_octave "f#" 6)
(N_defheight ,_octave "ges" 6)
(N_defheight ,_octave "g" 7)
(N_defheight ,_octave "g#" 8)
(N_defheight ,_octave "as" 8)
(N_defheight ,_octave "a" 9)
(N_defheight ,_octave "a#" 10)
(N_defheight ,_octave "b" 10)
(N_defheight ,_octave "h" 11)))
Your macro code is instead computing all expansions and throwing them away except the last one (as always happens for all forms except last one in a function body).
Note that probably this is one of the case in which eval-when
comes into play but I cannot really suggest anything about it because I've yet to truly understand all its intricacies (and I'm not even sure I want to :-) )
Upvotes: 4