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