我正在读温斯顿写的书“Lisp”。此外,我正在使用SBCL,Emacs,和泥。
在第8章(关于宏)中,本书有以下练习:
问题8-7:堆栈是一组学习有序的东西,可以使用push和pop操作访问。我们的推送将一个新项添加到堆栈的顶部,而我们的POP则删除堆栈顶部的项。列表可以用来表示堆栈,第一个元素对应于顶部的项。将我们的推送和弹出定义为宏。我们的推送包含两个参数,要推送的项和变量的名称,变量的值为表示堆栈的列表。返回的值是扩大的列表。我们的POP只接受一个元素,即值在列表中的变量的名称。返回的值是弹出的项。在这两种情况下,变量的值都会被更改,以反映堆栈的新状态。
对于pop函数,我得到了正确的答案。然而,我在push的重新实现方面遇到了麻烦。这是答案:
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))它如预期的那样工作。我最初的回答与此相似:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,stack)))但是,这会生成意外的列表嵌套,如:
CL-USER> (defparameter so-stack '(3 4 5 6))
SO-STACK
CL-USER> so-stack
(3 4 5 6)
CL-USER> (our-push 2 so-stack)
(2 (3 4 5 6))然后,我想:“哦,这一定是你使用,@的那种情况”。因为,@创建了剪接行为。因此,我做到了:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,@stack)))然而,它的不工作。此外,Slime还抛出了一条我觉得奇怪的错误信息:
The value
SO-STACK
is not of type
LIST特别是因为:
CL-USER> (listp so-stack)
T有办法在这种情况下使用,@吗?为什么表示变量so-stack是而不是的slime实际上是一个列表?
发布于 2022-04-08 08:27:01
你已经到了工作水平。使用宏函数展开宏时,您将对源代码进行操作。
源代码是这个列表
(my-push thing foos)也就是说,有三个符号的列表,名为my-push、thing和foos.
宏函数接受此列表并将其转换为不同的列表,在您的示例中:
(setf foos (list thing foos))然后进一步编译(更多宏扩展,最后编译到机器代码)。
如果您试图拼接foos,这是不可能的,因为符号不是列表。
宏函数从未看到这些符号的含义。它甚至在表单被编译成机器代码之前就完成了。
发布于 2022-04-08 09:15:37
这是答案:
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))正如你所说,你最初的回答是相似的:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,stack)))本质上,这就是问题所在。cons就像一根胶水管--它把一个值粘在一个现有的列表上。list只是一个大麻袋,我可以把东西放进去。
(list 5 '(1 2 3 4)) ; puts 5 and '(1 2 3 4) into a single list, side by side
; => (5 (1 2 3 4))
(cons 5 '(1 2 3 4)) ; attaches 5 to list '(1 2 3 4)
; => (5 1 2 3 4)通常,你使用列表-只是在这种情况下不使用。
然后尝试使用@将这些值拼接在一起。它没有起作用。
(defmacro our-push-splice (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,@stack)))找出它不起作用的最好方法是使用宏展开-1。这表明了宏实际上在做什么,而不是它应该做什么。
CL-USER> (defparameter stack '(1 2 3 4))
STACK
CL-USER> (macroexpand-1 '(our-push 5 stack))
(SETF STACK (LIST 5 STACK))
T
CL-USER> (macroexpand-1 '(our-push-splice 5 stack))
(SETF STACK (LIST 5 . STACK))
T
CL-USER> (our-push 5 stack)
(5 (1 2 3 4))
CL-USER> (our-push-splice 5 stack)
; Evaluation aborted on #<TYPE-ERROR expected-type: LIST datum: STACK>.因此,剪接的尝试失败了,因为在计算宏时,似乎预期堆栈会被拼接。名字是插入的,仅此而已。
发布于 2022-04-08 09:26:45
Svante的回答是正确的:宏是转换源代码的函数。但有时,通过让宏打印它们正在做的事情来看到这一点是很有用的。这里有一个简单的黑客来做这个(其他人的一个更一般的黑客是这里)。
(defmacro define-traced-macro (name args &body forms)
;; Define a traced macro. This probably misses edge cases
`(progn
(defmacro ,name ,args ,@forms)
(let ((m (macro-function ',name)))
(setf (macro-function ',name)
(lambda (form environment)
;; Do it like this so we get the source form even if
;; the macro fails
(let ((*print-pretty* t))
(format *trace-output* "~&~S~%" form))
(let ((*print-pretty* t)
(expansion (funcall m form environment)))
(format *trace-output* "~& -> ~S~%" expansion)
expansion))))
',name))现在,如果用define-traced-macro而不是defmacro定义宏,它将跟踪它的展开:
(define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))> (let ((x '(1 2)))
(book-our-push 3 x)
x)
(book-our-push 3 x)
-> (setq x (cons 3 x))
(3 1 2)这应该能令人信服地说明为什么你的版本不能工作。
顺便说一句,一旦您拥有了define-traced-macro,您就可以使用它来重新定义自己(要认真地这样做,您可能希望通过将自己的扩展硬连接到源中来定义它):
> (define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
(define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
-> (progn
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
(let ((m (macro-function 'book-our-push)))
(setf (macro-function 'book-our-push)
(lambda (form environment)
(let ((*print-pretty* t))
(format *trace-output* "~&~S~%" form))
(let ((*print-pretty* t)
(expansion (funcall m form environment)))
(format *trace-output* "~& -> ~S~%" expansion)
expansion))))
'book-our-push)
book-our-push这是很有趣的,如果这是你娱乐的那种东西。
https://stackoverflow.com/questions/71788178
复制相似问题