Я писал некоторые простые тестовые сценарии для одного из моих присвоений и создал что-то вроде набора тестов с помощью макросов. Я имею run-test
и run-test-section
и так далее. Я хотел бы run-test-section
взять много параметров, которые являются run-test
вызовы и подсчитывают количество ПЕРЕДАЧ и СБОЕВ.
run-test
возвраты T на ПЕРЕДАЧЕ и NIL на СБОЕ.
Что я надеюсь делать, прямо сейчас запись макрос, который берет a &REST
параметр, и вызывает каждый из элементов этого списка, наконец возвращая количество Истинных значений.
Это - то, что я в настоящее время имею:
(defmacro count-true (&rest forms)
`(cond
((null ,forms)
0)
((car ,forms)
(1+ (count-true (cdr ,forms))))
(T
(count-true (cdr ,forms)))))
Однако это помещает мой REPL в бесконечный цикл. Мог бы кто-то смочь указать, как я могу эффективнее управлять аргументами. Это - даже хорошая идея? Существует ли лучший подход?
править:
Как отмечен в ответах, макрос не нужен в этом случае. Используя встроенное COUNT
будет достаточен. Существует полезная информация в ответах на рекурсивных макро-вызовах, как бы то ни было.
Во время раскрытия макроса cdr
не оценивается. Итак, (count-true t t nil)
попадает в бесконечное расширение, подобное этому:
(count-true t t nil)
=>
(1+ (count-true (cdr (t t t nil))))
=>
(1+ (1+ (count-true (cdr (cdr (t t t nil))))))
=>
(1+ (1+ (1+ (count-true (cdr (cdr (cdr (t t t nil))))))))
=> ...
Ну, на самом деле это происходит для обеих рекурсивных ветвей одновременно. Так что он взрывается даже быстрее, чем пример.
Лучшая идея?
Сначала попробуйте написать то же самое, что и функцию. Выясните, где вы должны поместить лямбды, чтобы отложить оценку. Затем абстрагируйте функцию в макрос, чтобы вы могли опустить лямбды.
Смысл написания функции в первую очередь заключается в том, что иногда вы обнаруживаете, что функция достаточно хороша. Писать макрос вместо функции - ошибка.
Обычно, когда вы пишете макросы на Common Lisp, начинайте с цикла
, а не с рекурсии. Рекурсивные макросы сложны (и обычно ошибочны :).
Изменить:
Вот более правильный (но гораздо более длинный) пример:
(count-true t nil) =>
(cond
((null '(t nil)) 0)
((car '(t nil)) (1+ (count-true (cdr '(t nil)))))
(T (count-true (cdr '(t nil)))))
=>
(cond
((null '(t nil)) 0)
((car '(t nil)) (1+ (1+ (count-true (cdr (cdr '(t nil)))))))
(T (count-true (cdr (cdr '(t nil))))))
=>
(cond
((null '(t nil)) 0)
((car '(t nil)) (1+ (1+ (1+ (count-true (cdr (cdr (cdr '(t nil)))))))))
(T (count-true (cdr (cdr (cdr '(t nil)))))))
=>
(cond
((null '(t nil)) 0)
((car '(t nil)) (1+ (1+ (1+ (1+ (count-true (cdr (cdr (cdr (cdr '(t nil)))))))))))
(T (count-true (cdr (cdr (cdr (cdr '(t nil))))))))
Проблема в том, что ваш макрос расширяется до бесконечной формы. Система попытается расширить его весь во время фазы расширения макроса и, таким образом, должна закончиться память.
Обратите внимание, что тест на конец списка форм никогда не оценивается, а выражается в виде кода. Он должен находиться за пределами выражения обратного перехода. И, как объяснил другой человек, (cdr forms) должен быть оценен во время расширения макроса, а не оставлен как код для компиляции.
Т.е. что-то вроде этого (не проверено):
(defmacro count-true (&rest forms)
(if forms
`(if (car ',forms)
(1+ (count-true ,@(cdr forms)))
(count-true ,@(cdr forms)))
0))
Я считаю, что у вас два ложных впечатления относительно того, что такое макросы.
Макросы написаны для расширения, функции для выполнения. Если вы напишете рекурсивный макрос, он будет рекурсивно расширяться, не выполняя ничего из создаваемого им кода. Макрос - это совсем не что-то вроде встроенной функции!
Когда вы пишете макрос, он может расширяться до вызовов функций. Когда вы пишете макрос, вы не рискуете попасть в «страну макросов», где функции будут недоступны. Нет никакого смысла записывать count-true
в качестве макроса.
Забудьте о рекурсивных макросах. Они неприятны и действительно только для продвинутых пользователей Lisp.
Простая нерекурсивная версия:
(defmacro count-true (&rest forms)
`(+
,@(loop for form in forms
collect `(if ,form 1 0))))
CL-USER 111 > (macroexpand '(count-true (plusp 3) (zerop 2)))
(+ (IF (PLUSP 3) 1 0) (IF (ZEROP 2) 1 0))
Вот рекурсивный макрос:
(defmacro count-true (&rest forms)
(if forms
`(+ (if ,(first forms) 1 0)
(count-true ,@(rest forms)))
0))
Как уже говорили другие, избегайте рекурсивных макросов. Если вы хотите сделать это в функции, вы можете использовать apply
:
(defun count-true (&rest forms)
(cond
((null forms) 0)
(t (+ 1 (apply #'count-true (cdr forms))))))