【问题标题】:How to assign multiple constants within one macro call如何在一个宏调用中分配多个常量
【发布时间】:2011-08-23 17:46:21
【问题描述】:

我想在一个宏调用中分配多个常量。但是下面的代码只分配了最后一个常量,之前定义的常量是不可用的。

; 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 调用,而不仅仅是最后一个?

【问题讨论】:

    标签: macros lisp common-lisp


    【解决方案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)))
    
    • 带前导下划线的变量有什么用途?这是 Common Lisp 中不常见的 Lisp 实践
    • READ-FROM-STRING 可能会替换为 INTERN 和 STRING-UPCASE。
    • 您可能希望控制由 INTERN(或 READ-FOR-STRING)生成的符号的包。
    • EVAL 可以替换为 SYMBOL-VALUE
    • CONCATENATE 可以替换为 FORMAT: (format 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 这样的标识符作为常量注释值的全局名称,那么您没有问题 - 您只需要了解,然后使用 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*)
    

    【讨论】:

    • 有时我想知道为什么 Lisp 不是更主流。但很快答案就出来了:它是聪明人的语言。
    • 谢谢@Rainer,这个周末我会重写程序。
    【解决方案2】:

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

    【讨论】:

    • 啊,我之前试过(defmacro N_octave(_octave) (progn `(N_defheight ...
    【解决方案3】:

    在 Lisp 中,多个语句不是以这种方式连接的。尝试使用progn 构造:

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

    【讨论】:

      猜你喜欢
      • 2014-02-28
      • 2015-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-09
      • 1970-01-01
      • 1970-01-01
      • 2014-09-10
      相关资源
      最近更新 更多