Rud*_*udi 4 lisp macros common-lisp
我想在一个宏调用中分配多个常量.但是下面的代码只分配了最后一个常量,之前定义的常量不可用.
; 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")
Run Code Online (Sandbox Code Playgroud)
在sbcl中加载文件后,我只有h0常量,但没有c0..b0常量.
$ 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]
Run Code Online (Sandbox Code Playgroud)
那么如何更改宏来执行所有defconstant
调用,而不仅仅是最后一个?
其他答案已经指出了正确的解决方案:使用PROGN.
这里有一些关于'风格'的评论:
(defmacro N_defheight(_oct _note _offset)
`(defconstant ,(read-from-string (concatenate 'string _note _oct))
,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
_offset)))
Run Code Online (Sandbox Code Playgroud)
例:
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
...
(N_defheight ,_octave "h" 11))
Run Code Online (Sandbox Code Playgroud)
通过简单的迭代可以简化上面的内容:
`(progn
,@(loop for (note offset) in '(("c" 0) ("c#" 1) ... ("h" 11))
collect (list 'defheight octave note offset)))
Run Code Online (Sandbox Code Playgroud)
或使用MAPCAR
`(progn
,@(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
'(("c" 0) ("c#" 1) ... ("h" 11))))
Run Code Online (Sandbox Code Playgroud)
效果是输入较少,重要符号只写一次.人们必须决定什么是更好的:许多看似相似的陈述或改变数据描述的小程序.
但还有另一个问题:数据被编码到宏中.
这是错的.宏应该进行代码转换而不包含数据.再一次,你可以做任何事情,但好的Lisp需要对编程风格有一些感觉.我会将注释和偏移作为列表放入变量中并在宏中使用它,或者将其作为参数提供:
(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*)
Run Code Online (Sandbox Code Playgroud)
现在还有另一个问题.我们用名称来定义常量C0
.Lisp中的常量总是指全局常量值.不允许重新绑定.这意味着C0不再是程序中有效的本地变量名.如果你知道你永远不会使用C0作为变量名,那很好 - 但是在维护期间以后可能不知道这个问题.因此,在这样的常量名称周围添加加号是很好的风格:+C0+
.再一次,只是一个惯例.您还可以使用自己的专用命名约定,该约定不应与您的变量名称冲突.喜欢NOTE-C0
.
如果您的目的是始终使用标识符c0
作为常量注释值的全局名称,那么您就没有问题 - 您只需要了解它,然后使用DEFCONSTANT,您不能再使用它c0
作为变量.那么拥有自己的包可能是个好主意.
下一步:当您想在计算宏扩展时使用变量时,您需要确保变量具有值.之前加载文件或使用EVAL-WHEN.
这导致了这段代码:
(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*)
Run Code Online (Sandbox Code Playgroud)