Macros 如何在一个宏调用中分配多个常量
我想在一个宏调用中分配多个常量。但是下面的代码只分配最后一个常量,以前定义的常量不可用Macros 如何在一个宏调用中分配多个常量,macros,lisp,common-lisp,Macros,Lisp,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_oc
; 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")
在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]
$sbcl
这是SBCL 1.0.40.0.debian,是ANSI Common Lisp的一个实现。
有关SBCL的更多信息,请访问。
SBCL是免费软件,按原样提供,绝对没有保修。
它主要是在公共领域;某些部分在以下章节中提供:
BSD风格的许可证。请参阅中的学分和复制文件
分发以获取更多信息。
*(加载“注释”)
T
*h0
11
*c0
在线程#中的未绑定变量上调用调试器:
变量C0未绑定。
键入“帮助”以获取调试器帮助,或键入(SB-EXT:QUIT)以退出SBCL。
重新启动(可通过数字或可能的缩写名称调用):
0:[中止]退出调试器,返回顶层。
(SB-INT:SIMPLE-EVAL-IN-lexev C0#)
0]
因此,如何更改宏以执行所有
defcontent
调用,而不仅仅是最后一个调用?您需要扩展到progn
表单
(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)))
相反,您的宏代码计算所有扩展并丢弃它们,但最后一个除外(函数体中除最后一个之外的所有形式都是如此)
请注意,这可能是当发挥作用时,
eval的一种情况,但我不能真正对此提出任何建议,因为我还没有真正理解它的所有复杂之处(我甚至不确定我是否想要:-)在Lisp中有几个语句不是以这种方式连接在一起的。尝试使用progn
结构:
(defmacro N_octave(_octave)
`(progn (N_defheight ,_octave "c" 0)
(N_defheight ,_octave "c#" 1)
... ))
其他答案已经指出了正确的解决方案:使用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)))
- 带前导下划线的变量有什么用途?这不是CommonLisp中常见的Lisp实践
- 从字符串读取可能被INTERN和STRING-UPCASE替换
- 您可能希望控制INTERN生成的符号(或READ-for-STRING)的包
- EVAL可以用SYMBOL-VALUE替换
- 连接可替换为格式:(格式nil“N-OCT~a”OCT)
- 对于定义宏,DEF应该是名称的开头。那只是个惯例
例如:
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
...
(N_defheight ,_octave "h" 11))
可以通过简单的迭代简化上述内容:
`(progn
,@(loop for (note offset) in '(("c" 0) ("c#" 1) ... ("h" 11))
collect (list 'defheight octave note offset)))
或者使用MAPCAR
`(progn
,@(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
'(("c" 0) ("c#" 1) ... ("h" 11))))
效果是打字更少,重要的符号只写一次。
我们必须决定什么更好:许多类似的语句或一个转换数据描述的小程序
但是还有一个问题:数据被编码到宏中。
这是错误的。宏应该进行代码转换,而不包含数据。同样,您可以做任何事情,但是好的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*)
现在还有一个问题。我们用C0
这样的名称定义常量。Lisp中的常量总是指全局常量值。不允许重新绑定。这意味着C0在程序中不再是有效的局部变量名。如果您知道永远不会使用C0作为变量名,这很好,但在以后的维护过程中可能不知道这个问题。出于这个原因,在常量名称周围加上加号是一种很好的样式,例如:+C0+
。再说一次,只是一个惯例。您还可以使用自己的专用命名约定,该约定不应与变量名称冲突。如NOTE-C0
如果您的目的是始终使用像c0
这样的标识符作为常量注释值的全局名称,那么您没有问题-您只需要了解,使用defcontent时,您不能再将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*)
啊,我以前试过(defmacro N\u octave(\u octave)(progn`(N\u defheight…
),有时候我想知道为什么Lisp不是更主流。但很快就有了答案:它是一种智能语言。谢谢@Rainer,我会在这个周末重写这个程序。