Макрос Lisp, который выполняет «разворачивание цикла»

#macros #lisp

#макросы #lisp

Вопрос:

Мои первые шаги с макросами Lisp…

 (defconstant width 7)
(defconstant height 6)
...
; board is a 2D array of width x height
; and this is my first ever macro:
(defmacro at (y x)
  `(aref board ,y ,x))
; "board" must be available wherever the macro is used.

(defun foo (board ...)
  ...
  (loop for y from 0 to (1- height) do
    ; thanks to the "at" macro, this is cleaner:
    (let ((score (  (at y 0) (at y 1) (at y 2))))
      (loop for x from 3 to (1- width) do
        (incf score (at y x))
        ; ...do something with score
        (decf score (at y (- x 3)))))))
  

В коде используется мой первый в истории макрос, «at». Он выдает «инструкции доступа» для чтения с платы [y] [x], поэтому его можно использовать только в тех местах, где существует «плата», как функция «foo» выше.

Это сработало — и тогда я понял это… Я могу пойти дальше.

Два вложенных цикла «статически» ограничены: от 0 до высоты-1 для y, от 3 до (ширина-1) для x … так что теоретически я могу создать макрос, который выдает (разворачивает!) точные инструкции incf и decf, выполняемые в коде циклов!

Я пробовал это:

 (defmacro unroll ()
  (loop for y from 0 to (1- height) do
    `(setf score (  (at ,y 0)  (at ,y 1) (at ,y 2)))
    (loop for x from 3 to (1- width) do
     `(incf score (at ,y ,x))
     `(decf score (at ,y (- ,x 3))))))
  

…но не удалось — «(macroexpand-1 ‘(развернуть))» показывает мне НОЛЬ.

Что я делаю не так?

В случае, если это неясно, я хочу использовать два вложенных цикла и выдавать «код» в начале внешнего цикла и для каждой итерации внутреннего цикла.

Любая помощь наиболее ценится (я новичок в LISP).

ОБНОВЛЕНИЕ: После доброго совета @larsmans мне удалось применить это изменение к своему коду — и, к моему огромному удовлетворению, я наблюдал, как версия моего алгоритма Score4 на Lisp стала 2-й по быстродействию реализацией, уступая только C и C (и быстрее, чем OCaml!).

Ответ №1:

Вы должны collect генерировать инструкции внутри макроса loop , а не притворяться, что выполняете их с do :

 (defmacro unroll ()
  (loop for y from 0 to (1- height)
        collect
          `(begin (setf score (  (at ,y 0)  (at ,y 1) (at ,y 2)))
                  ,@(loop for x from 3 to (1- width)
                          collect `(begin (incf score (at ,y ,x))
                                          (decf score (at ,y (- ,x 3))))))))