将文档字符串合并到 def* 宏中的正确方法?

Correct way to incorporate a docstring in a def* macro?

我正在学习 Practical Common Lisp。我得到了你定义一个 deftest 宏的例子,它像 defun 一样工作,但有一些附加功能。这让我想到能够添加文档字符串会很好。我发现以下两项都有效,但其中一项更正确吗?是否有一种“正确”的方法来实现 defun 的类似可选文档字符串的行为?

(defmacro deftest (name parameters &body body)
  (let ((docstring ""))
    (when (stringp (car body)) (setf docstring (car body) body (cdr body)))
    `(defun ,name ,parameters
       ,docstring
       (let ((*test-name* (append *test-name* (list ',name))))
         ,@body))))

(defmacro deftest (name parameters &optional docstring &body body)
  (when (not (stringp docstring)) (setf docstring ""))
  `(defun ,name ,parameters
     ,docstring
     (let ((*test-name* (append *test-name* (list ',name))))
       ,@body)))

一般来说,您可能希望从函数体中解析出可能的文档字符串和任何声明,这样您就可以将声明放在它们所属的位置。我使用这样的东西:

(defun parse-body (body)
    (multiple-value-bind (docstring decls/forms)
        (if (stringp (first body))
            (values (first body) (rest body))
          (values nil body))
      (loop for remainder on decls/forms
            while (and (not (null remainder))
                       (consp (first remainder))
                       (eql (car (first remainder)) 'declare))
            collect (first remainder) into decls
            finally (return (values docstring decls remainder)))))

然后你的 deftest 将是

(defmacro deftest (name parameters &body body)
  (multiple-value-bind (docstring decls forms) (parse-body body)
    `(defun ,name ,parameters
       ,@(if docstring (list docstring) '())
       ,@decls
       (let ((*test-name* (append *test-name* (list ',name))))
         ,@forms))))

我希望我可以说我有一个标准的 parse-body 功能,但我没有:我每次都写一个新的。