Как записать, что рекурсивный макрос обращается к параметру &REST в Lisp?

Я писал некоторые простые тестовые сценарии для одного из моих присвоений и создал что-то вроде набора тестов с помощью макросов. Я имею 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 будет достаточен. Существует полезная информация в ответах на рекурсивных макро-вызовах, как бы то ни было.

6
задан Svante 11 February 2010 в 23:08
поделиться

5 ответов

Во время раскрытия макроса 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))))))))
=> ...

Ну, на самом деле это происходит для обеих рекурсивных ветвей одновременно. Так что он взрывается даже быстрее, чем пример.

Лучшая идея?

  1. Сначала попробуйте написать то же самое, что и функцию. Выясните, где вы должны поместить лямбды, чтобы отложить оценку. Затем абстрагируйте функцию в макрос, чтобы вы могли опустить лямбды.

    Смысл написания функции в первую очередь заключается в том, что иногда вы обнаруживаете, что функция достаточно хороша. Писать макрос вместо функции - ошибка.

  2. Обычно, когда вы пишете макросы на 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))))))))
5
ответ дан 8 December 2019 в 16:02
поделиться

Проблема в том, что ваш макрос расширяется до бесконечной формы. Система попытается расширить его весь во время фазы расширения макроса и, таким образом, должна закончиться память.

Обратите внимание, что тест на конец списка форм никогда не оценивается, а выражается в виде кода. Он должен находиться за пределами выражения обратного перехода. И, как объяснил другой человек, (cdr forms) должен быть оценен во время расширения макроса, а не оставлен как код для компиляции.

Т.е. что-то вроде этого (не проверено):

(defmacro count-true (&rest forms)
  (if forms
      `(if (car ',forms)
           (1+ (count-true ,@(cdr forms)))
         (count-true ,@(cdr forms)))
    0))
3
ответ дан 8 December 2019 в 16:02
поделиться

Я считаю, что у вас два ложных впечатления относительно того, что такое макросы.

Макросы написаны для расширения, функции для выполнения. Если вы напишете рекурсивный макрос, он будет рекурсивно расширяться, не выполняя ничего из создаваемого им кода. Макрос - это совсем не что-то вроде встроенной функции!

Когда вы пишете макрос, он может расширяться до вызовов функций. Когда вы пишете макрос, вы не рискуете попасть в «страну макросов», где функции будут недоступны. Нет никакого смысла записывать count-true в качестве макроса.

2
ответ дан 8 December 2019 в 16:02
поделиться

Забудьте о рекурсивных макросах. Они неприятны и действительно только для продвинутых пользователей 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))
4
ответ дан 8 December 2019 в 16:02
поделиться

Как уже говорили другие, избегайте рекурсивных макросов. Если вы хотите сделать это в функции, вы можете использовать apply:

(defun count-true (&rest forms)
  (cond
    ((null forms) 0)
    (t (+ 1 (apply #'count-true (cdr forms))))))
0
ответ дан 8 December 2019 в 16:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: