(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)
如您所见,代码现在尝试使用参数b 和c 调用函数a。
如果您在宏中引用 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/or 的编写方式,具有 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 的返回值必须是新值。在某些情况下,它可能会导致风格不佳,但在这里会很好。