Lisp: может ли макрос быть рекурсивным?

Я недавно начал кодировать на Лиспе, и уже очень впечатлен макросами - они позволили мне делать сложные разворачивание цикла во время компиляции, что-то, что я не могу сделать элегантно на любом другом языке, о котором я знаю (т.е. генерировать код с сохранением исходной структуры).

По поводу оптимизации: я добавил аннотации типов (много «фиксированного числа») в один и тот же код. Как только я добавил 3 или 4 из них, я понял, что делаю это неправильно - для этого нужны макросы, Dont Repeat Yourself ...

; whenever we want to indicate that the result of an operation 
; fits in a fixnum, we macro expand (the fixnum (...))
(defmacro fast (&rest args)
  `(the fixnum ,args))
...
(cond
  (...)
  (t (let* ((forOrange (+ (aref counts 5)
                          (fast * 2 (aref counts 6))
                          (fast * 5 (aref counts 7))
                          (fast * 10 (aref counts 8))))
            (forYellow (+ (aref counts 3)
                          (fast * 2 (aref counts 2))
                          (fast * 5 (aref counts 1))
                          (fast * 10 (aref counts 0))))

... и действительно, это сработало: вместо того, чтобы писать много " (fixnum (...)) "везде, я просто быстро ставлю перед выражением префикс" быстро "- и все хорошо.

Но потом ...

Я понял, что даже на этом нельзя останавливаться: в принципе, макрос "fast" должен ... вызываться в начале оценки, в данном случае:

            (forYellow (fast + (aref counts 3)
                          (* 2 (aref counts 2))
                          (* 5 (aref counts 1))
                          (* 10 (aref counts 0))))

... и он должен рекурсивно "сажать" "(fixnum (...))" во всех подвыражениях.

Можно ли это сделать? Может ли "дефмакро" быть рекурсивным?

ОБНОВЛЕНИЕ : Я столкнулся с некоторыми действительно странными проблемами, пытаясь сделать это, поэтому в итоге я сделал то, что Рорд предложил ниже - т.е. реализовал функцию, протестировал ее в ответе и вызвал ее из макроса:

(defun operation-p (x)
  (or (equal x '+) (equal x '-) (equal x '*) (equal x '/)))

(defun clone (sexpr)
  (cond
    ((listp sexpr)
     (if (null sexpr)
       ()
       (let ((hd (car sexpr))
             (tl (cdr sexpr)))
         (cond
           ((listp hd) (append (list (clone hd)) (clone tl)))
           ((operation-p hd) (list 'the 'fixnum (cons hd (clone tl))))
           (t (cons hd (clone tl)))))))
    (t sexpr)))

(defmacro fast (&rest sexpr)
  `(,@(clone sexpr)))

И она отлично работает под SBCL:

$ sbcl
This is SBCL 1.0.52, an implementation of ANSI Common Lisp.
...
* (load "score4.cl")

T
* (setf a '(+ (1 2) (- 1 (+ 5 6)))
...
* (clone a)

(THE FIXNUM (+ (1 2) (THE FIXNUM (- 1 (THE FIXNUM (+ 5 6))))))

* (macroexpand '(fast + 1 2 THE FIXNUM (- 1 THE FIXNUM (+ 5 6))))

(THE FIXNUM (+ 1 2 THE FIXNUM (THE FIXNUM (- 1 THE FIXNUM (THE FIXNUM (+ 5 6))))))
T

Все хорошо, за исключением одного побочного эффекта: CMUCL работает, но больше не компилирует код:

; Error: (during macroexpansion)
; Error in KERNEL:%COERCE-TO-FUNCTION:  the function CLONE is undefined.

Ну что ж: -)

ОБНОВЛЕНИЕ : Ошибка компиляции была устранена и решена в другом вопросе SO .

8
задан ttsiodras 11 July 2017 в 11:50
поделиться