首先,这是我的第一个问题,对于任何不好的做法,我深表歉意,如果您告诉我我做错了,我将不胜感激。
我正在尝试编写一个宏以减少重复代码,该宏是在Common Lisp中创建一个程序包,系统或代码文件,其名称中带有章节编号。下面的代码是我所拥有的,当:chapter-number作为字符串传递时,它可以完美地工作,但是当作为数字传递时,它会出错:
(defmacro with-open-chapter-file
((streamvar (component &key
(type "lisp")
(directory (sb-posix:getcwd))
chapter-number))
(&body body))
`(let ((chapter-number ,(if (numberp chapter-number) ; the problem is at this if clause.
(write-to-string chapter-number) ; My intention was to convert it to a string if it was a number or leave it as is otherwise.
chapter-number)))
(with-open-file (,streamvar (make-pathname
:name ,(if chapter-number ; the variable manipulated in the if clause is used in this expression
(concatenate 'string "chapter-" chapter-number "-" (string component))
component)
:type ,type
:defaults ,directory)
:direction :output)
,body)))
当我运行以下测试时:
(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 :(得分:4)
在代码中:
:name ,(if chapter-number ; the variable manipulated in the if clause is used in this expression
(concatenate 'string "chapter-" chapter-number "-" (string component))
component)
您正在对宏使用chapter-number
参数,而不是在扩展中与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))
另一种不需要测试chapter-number
类型的解决方案是将使用concatenate
的代码更改为使用format
:
(if chapter-number
(format nil "chapter-%A-%A" chapter-number component)
component)
一个不相关的错误是您应该使用,@body
来替换主体,因为它是一个必须被拼接到表达式中的列表。
答案 1 :(得分:1)
宏的一个典型问题是要理解,它们通常处理代码:它们接收代码并产生代码。通常,他们不知道变量的值,因为代码尚未运行。
例如,设想:
(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!"))
但是由于format
在打印过程中仍然进行了转换,因此我们可以删除所有转换逻辑...
(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))))
现在,您需要确保component
,type
...不是不需要的运行时变量,然后这些变量从主体代码中可见...