Правильный способ включить строку документа в макрос def *?

#lisp #common-lisp #sbcl #defmacro

#шепелявый #common-лисп #sbcl #дефмакро

Вопрос:

Я прокладываю себе путь через Практический Common Lisp. Я добрался до примера, где вы определяете deftest макрос, который работает как defun с некоторыми дополнительными функциями. Это навело меня на мысль, что было бы неплохо иметь возможность добавить строку документа. Я обнаружил, что оба следующих варианта работают, но является ли один из них более правильным? Есть ли «правильный» способ добиться необязательного поведения, подобного docstring defun ?

 (defmacro deftest (name parameters amp;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 amp;optional docstring amp;body body)
  (when (not (stringp docstring)) (setf docstring ""))
  `(defun ,name ,parameters
     ,docstring
     (let ((*test-name* (append *test-name* (list ',name))))
       ,@body)))
 

Ответ №1:

В общем случае вы, вероятно, захотите проанализировать как возможную строку документа, так и любые объявления из тела функции, чтобы вы могли поместить объявления туда, где они принадлежат. Я использую что-то вроде этого:

 (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 amp;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 функция, но у меня ее нет: я просто каждый раз пишу новую.

Комментарии:

1. Спасибо, это четкий ответ. Есть только одна часть, которая меня все еще смущает. Почему необходимо / предпочтительнее использовать ,@(list docstring) вместо ,docstring alone?

2. @bashfuloctopus Смотри также common-lisp.net/project/parse-declarations/manual/html_node /…

3. @bashfuloctopus: извините, это была ошибка: у меня была рабочая версия, а потом я подумал: «О, нет, я могу быть умным и избежать условного выражения» (чего я не могу), что привело к неработающей версии, которую я никогда не тестировал. Трюк, который он теперь делает правильно, заключается в том, чтобы использовать ,@ для сращивания в пустом списке (т. Е. Ничего, поскольку он сращивается), если нет строки документа, в противном случае для сращивания в одноэлементном списке строки документа, что означает ввод строки документа. Извините за это.

4. @tfb хитрый! Я хакерски предоставлял функции пустую строку документа, а не объединял пустой список.

5. @coredump parse-body выглядит удобно, но я не уверен, как получить к нему доступ. Я не вижу ссылки для скачивания, и я не смог загрузить ее с помощью (asdf:load-system 'parse-declarations) . Что я упускаю?