Macros 嵌套在let中的if子句在宏中不能按预期工作

Macros 嵌套在let中的if子句在宏中不能按预期工作,macros,common-lisp,Macros,Common Lisp,好的,首先这是我的第一个问题,所以我为任何不好的做法道歉,如果你告诉我我做错了什么,我将不胜感激 我正在尝试编写一个宏来减少重复代码,这是在CommonLisp中创建一个名称中带有章节号的包、系统或代码文件。下面的代码就是我所拥有的,它在以下情况下工作得很好:章节号作为字符串传递,但在作为数字传递时出错: (打开章节文件的defmacro ((组件和键) (输入“lisp”) (目录(sb posix:getcwd)) 章号( (&body)) `(让((章号)(if)(章号);问题在于本if条

好的,首先这是我的第一个问题,所以我为任何不好的做法道歉,如果你告诉我我做错了什么,我将不胜感激

我正在尝试编写一个宏来减少重复代码,这是在CommonLisp中创建一个名称中带有章节号的包、系统或代码文件。下面的代码就是我所拥有的,它在以下情况下工作得很好:章节号作为字符串传递,但在作为数字传递时出错:

(打开章节文件的defmacro
((组件和键)
(输入“lisp”)
(目录(sb posix:getcwd))
章号(
(&body))
`(让((章号)(if)(章号);问题在于本if条款。
(写入字符串章节号);我的意图是将其转换为字符串(如果它是一个数字),或者保持原样。
章号))
(打开文件(,streamvar(生成路径名
:name,(if章节号;if子句中操纵的变量在此表达式中使用
(连接“字符串”章节-“章节号”-“(字符串组件))
(组成部分)
字体,字体
:默认值,目录)
:方向:输出)
(正文)
当我运行以下测试时:

(macroexpand-1 '(with-open-chapter-file (out ("pack" :chapter-number 10))
                   (format t "Hey!")))
我得到一个错误:

The value
  10
is not of type
  SEQUENCE
   [Condition of type TYPE-ERROR]
以及回溯:

  0: (LENGTH 10)
  1: (SB-KERNEL:%CONCATENATE-TO-STRING "chapter-" 10 "-" "pack")
  2: ((MACRO-FUNCTION WITH-OPEN-CHAPTER-FILE) (WITH-OPEN-CHAPTER-FILE (OUT ("pack" :CHAPTER-NUMBER 10)) (FORMAT T "Hey!")) #<unused argument>)
  3: ((FLET SB-IMPL::PERFORM-EXPANSION :IN MACROEXPAND-1) #<FUNCTION (MACRO-FUNCTION WITH-OPEN-CHAPTER-FILE) {2278173B}> NIL)
  4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (MACROEXPAND-1 (QUOTE (WITH-OPEN-CHAPTER-FILE # #))) #<NULL-LEXENV>)
  5: (EVAL (MACROEXPAND-1 (QUOTE (WITH-OPEN-CHAPTER-FILE # #))))
0:(长度10)
1:(SB内核:%CONCATENATE-TO-STRING“第10章”-“包”)
2:((带-OPEN-CHAPTER-FILE的宏函数)(带-OPEN-CHAPTER-FILE(OUT(“pack”:第10章))(格式为T“Hey!”)#)
3:((FLET SB-IMPL::PERFORM-EXPANSION:IN-macroexpansion-1)#NIL)
4:(SB-INT:SIMPLE-EVAL-IN-lexev(MACROEXPAND-1(引号(WITH-OPEN-CHAPTER-FILE##)))
5:(EVAL(MACROEXPAND-1(引号(带-OPEN-CHAPTER-FILE##)))
如果你们能帮助我,我将不胜感激。

在代码中:

  :name ,(if chapter-number ; the variable manipulated in the if clause is used in this expression
             (concatenate 'string "chapter-" chapter-number "-" (string component)) 
           component)
您对宏使用的是
章节号
参数,而不是在展开中与
let
绑定的变量,因为此代码在逗号之后

您不应该在扩展中绑定该变量,而应该在宏本身中更新该变量

(defmacro with-open-chapter-file
  ((streamvar (component &key
                         (type "lisp") (directory (sb-posix:getcwd)) chapter-number))
   (&body body))
  (when (numberp chapter-number)
    (setq chapter-number (write-to-string chapter-number)))
  `(with-open-file (,streamvar (make-pathname
                                :name ,(if chapter-number
                                           (concatenate 'string "chapter-" chapter-number "-" (string component)) 
                                         component)
                                :type ,type
                                :defaults ,directory)
                                :direction :output)
     ,@body))
另一种不需要测试章节号类型的解决方案是将使用
连接的代码更改为使用
格式

(if chapter-number
    (format nil "chapter-%A-%A" chapter-number component)
    component)

一个不相关的错误是,您应该使用
,@body
来替换body,因为它是一个必须拼接到表达式中的列表。

宏的一个典型问题是理解它们通常处理代码:它们接收代码并生成代码。通常他们不知道变量的值,因为代码还没有运行

例如,想象一下:

(let ((n 10))
  (with-open-chapter-file (out ("pack" :chapter-number n))
    (format t "Hey!")))
现在在宏中没有通用的方法来知道
n
的值是什么。当宏窗体在编译过程中展开时,它会看到一个
n
,除此之外什么都没有

现在,当代码中有一个实际数字时,宏会将该数字视为源代码的一部分:

(with-open-chapter-file (out ("pack" :chapter-number 10)
  (format t "Hey!")))
现在我们可以问我们,在宏展开时识别数字并在宏展开时计算一些东西是否有意义?这是一种优化,可能不值得。现在,编译器可能会检测到它是一个常量,并且可以在编译时转换

因此,在您的示例中,可以在运行时将参数转换为字符串,而不是在宏扩展时进行转换

现在让我们假设代码如下所示:

(defmacro with-open-chapter-file
          ((streamvar (component
                       &key
                       (type "lisp")
                       (directory "/foo/")
                       chapter-number))
           (&body body))
  (when (numberp chapter-number)
    (setf chapter-number (write-to-string chapter-number)))
  `(let ((component ,component)
         (type ,type)
         (directory ,directory)
         (chapter-number ,chapter-number))
     (when (numberp chapter-number)
       (setf chapter-number (write-to-string chapter-number)))
     (with-open-file
         (,streamvar (make-pathname
                      :name (if chapter-number
                                (format nil
                                        "chapter-~a-~a"
                                        chapter-number
                                        component)
                              component)
                      :type type
                      :defaults directory)
                     :direction :output)
       ,@body)))
现在我们可以这样做:

a) 使用
n

CL-USER 6 > (pprint (macroexpand-1 '(with-open-chapter-file (out ("pack" :chapter-number n))
                                                            (format t "Hey!"))))

(LET ((COMPONENT "pack") (TYPE "lisp") (DIRECTORY "/foo/") (CHAPTER-NUMBER N))
  (WHEN (NUMBERP CHAPTER-NUMBER) (SETF CHAPTER-NUMBER (WRITE-TO-STRING CHAPTER-NUMBER)))
  (WITH-OPEN-FILE (OUT
                   (MAKE-PATHNAME :NAME
                                  (IF CHAPTER-NUMBER
                                      (FORMAT NIL "chapter-~a-~a" CHAPTER-NUMBER COMPONENT)
                                    COMPONENT)
                                  :TYPE
                                  TYPE
                                  :DEFAULTS
                                  DIRECTORY)
                   :DIRECTION
                   :OUTPUT)
    FORMAT
    T
    "Hey!"))
b)使用
10

CL-USER 7 > (pprint (macroexpand-1 '(with-open-chapter-file (out ("pack" :chapter-number 10))
                                                            (format t "Hey!"))))

(LET ((COMPONENT "pack") (TYPE "lisp") (DIRECTORY "/foo/") (CHAPTER-NUMBER "10"))
  (WHEN (NUMBERP CHAPTER-NUMBER) (SETF CHAPTER-NUMBER (WRITE-TO-STRING CHAPTER-NUMBER)))
  (WITH-OPEN-FILE (OUT
                   (MAKE-PATHNAME :NAME
                                  (IF CHAPTER-NUMBER
                                      (FORMAT NIL "chapter-~a-~a" CHAPTER-NUMBER COMPONENT)
                                    COMPONENT)
                                  :TYPE
                                  TYPE
                                  :DEFAULTS
                                  DIRECTORY)
                   :DIRECTION
                   :OUTPUT)
    FORMAT
    T
    "Hey!"))
但由于
格式
在打印过程中会进行转换,因此我们可以删除所有转换逻辑

(defmacro with-open-chapter-file
          ((streamvar (component
                       &key
                       (type "lisp")
                       (directory "/foo/")
                       chapter-number))
           (&body body))
  `(let ((component      ,component)
         (type           ,type)
         (directory      ,directory)
         (chapter-number ,chapter-number))
     (let ((name (if chapter-number
                     (format nil
                             "chapter-~a-~a"
                             chapter-number
                             component)
                   component)))
       (with-open-file (,streamvar (make-pathname
                                    :name     name
                                    :type     type
                                    :defaults directory)
                                   :direction :output)
         ,@body))))

现在您需要确保
组件
类型
。。。不是从主体代码中可见的不需要的运行时变量

第一句话对我帮助很大。我从来没有想过在宏扩展期间不进行计算。。。非常感谢。而且,只是为了确保,第一个让你包括只是为了美学的目的,对吗?你能像我一样手工用逗号吗?(我知道美学很重要,我认为你的方式比我的好,只是想确定一下。)@GabrielSollero:人们实际上会在LET中使用生成的独特符号。有时,控制执行顺序很有用,然后使用LET可能很有用,因为执行是自上而下的。另一件事是LET可能很有用->如果您想多次使用传递的表达式中的值,那么确保该值实际上只计算一次可能很有用。