我试图创建一个简单的备忘录。如何在此代码中阻止评估args表单?
(defmacro defun/memo (name args &rest body)
`(let ((memo (make-hash-table :test 'equalp)))
(defun ,name ,args
(if (gethash (loop for x in ,args collect x) memo)
(gethash (loop for x in ,args collect x) memo)
(let ((result (progn ,@body)))
(setf (gethash (loop for x in ,args collect x) memo) result)
result)))))
错误:
; in: DEFUN ADD
; (X Y)
;
; caught STYLE-WARNING:
; undefined function: X
;
; compilation unit finished
; Undefined function:
; X
答案 0 :(得分:6)
(defmacro defun/memo (name args &rest body)
您通常使用&body body
声明正文,而不是&rest body
。
`(let ((memo (make-hash-table :test 'equalp)))
memo
符号将在生成的代码中结束。如果body
包含对memo
的引用,例如在defun/memo
调用之外被词法绑定的符号,则它将使用您的变量。您应该使用新的符号,使用gensym
(在反引号之外)在宏内部生成。例如,您可以执行以下操作以避免两次评估expr
:
(let ((var-expr (gensym)))
`(let ((,var-expr ,expr))
(+ ,var-expr ,var-expr)))
(if (gethash (loop for x in ,args collect x) memo)
(gethash (loop for x in ,args collect x) memo)
(let ((result (progn ,@body)))
(setf (gethash (loop for x in ,args collect x) memo) result)
result)))))
以下应该做什么?
(loop for x in ,args collect x)
假设您使用(defun/memo test (a b c) ...)
定义一个函数,您将在上面注入参数的文字列表,这将导致代码包含:
(loop for x in (a b c) collect x)
如您所见,代码现在尝试使用参数a
和b
调用函数c
。
如果您在宏中引用args
该怎么办?
(loop for x in ',args collect x)
然后,您将获得:
(loop for x in '(a b c) collect x)
现在,您只是复制一个文字列表。运行上面生成的代码时,它只会构建一个新的列表(a b c)
。这就是你需要的吗?
你想要的是获取你的函数的所有参数,即你给出的值列表。循环可以替换为:
(list ,@args)
哪个会扩展为:
(list a b c)
在这里,你有所有的价值观,在列表中。
但Common Lisp已经提供了一种将所有参数作为列表获取的方法:
(defun foo (&rest args)
;; args is bound to a list of values
)
您生成的函数也可以这样做。
此外,(if (gethash ...) (gethash ...) other)
可以写为(or (gethash ...) other)
。这样做的好处是只评估一次gethash
的调用。
更重要的是(感谢@Sylwester),因为您正在编写通用宏,所以您无法事先知道nil
是否可能是返回值。考虑到if /或是如何编写的,具有nil值将使结果每次重新计算。您需要使用gethash
的辅助返回值来检查元素是否存在:
(multiple-value-bind (value exists-p) (gethash ...)
(if exists-p
value
(setf (gethash ...) ...)))
此外,如果您的缓存函数返回多个值,您可能希望使用multiple-value-list
抓取所有值并使用values-list
返回它们。
顺便说一句,代码如下:
(let ((result expr))
(setf place result)
result)
......没有理由不写为:
(setf place expr)
setf
的返回值必须是新值。在某些情况下,它可能会导致糟糕的风格,但在这里会很好。