如何在一次宏调用中分配多个常量

时间:2011-08-23 17:46:21

标签: 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_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] 

那么如何更改宏以执行所有defconstant次调用,而不仅仅是最后一次?

3 个答案:

答案 0 :(得分:6)

其他答案已经指出了正确的解决方案:使用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)))
  • 带有前导下划线的变量有什么用途?这不是Common Lisp中常见的Lisp实践
  • READ-FROM-STRING可能会被INTERN和STRING-UPCASE取代。
  • 您可能希望控制INTERN(或READ-FOR-STRING)生成的符号的包。
  • EVAL可以替换为SYMBOL-VALUE
  • CONCATENATE可以替换为FORMAT :(格式为“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之类的标识符作为常量音符值的全局名称,那么您没有问题 - 您只需要了解那么使用DEFCONSTANT,您就可以' t不再使用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*)

答案 1 :(得分:4)

您需要扩展为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-when发挥作用的情况之一,但我无法真正提出任何有关它的建议,因为我还没有真正理解它的所有错综复杂(我甚至不确定我想:-))

答案 2 :(得分:2)

在Lisp中没有以这种方式连接多个语句。尝试使用progn构造:

(defmacro N_octave(_octave)
    `(progn (N_defheight ,_octave "c" 0)
            (N_defheight ,_octave "c#" 1)
            ... ))