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

发布于 2024-12-01 07:20:18 字数 1970 浏览 6 评论 0原文

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

高速公鹿 2024-12-08 07:20:19

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

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

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)
            ... ))
机场等船 2024-12-08 07:20:18

其他答案已经指出了正确的解决方案:使用 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 READ-FROM-STRING 中这种不常见的 Lisp 实践
  • 可能会被 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*)

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)))
  • Variables with leading underscore have what purpose? This not common Lisp practice in Common Lisp
  • READ-FROM-STRING might be replaced by INTERN and STRING-UPCASE.
  • You might want to control the package for the symbol generated by INTERN (or READ-FOR-STRING).
  • EVAL can be replaced by SYMBOL-VALUE
  • CONCATENATE could be replaced by FORMAT: (format nil "N-OCT~a" oct)
  • for a defining macro, DEF should be the beginning of the name. That's just a convention.

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*)
遗失的美好 2024-12-08 07:20:18

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

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 :-) )

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文