Frage Lisp: Kann ein Makro rekursiv sein?


Ich habe vor kurzem begonnen, in Lisp zu programmieren, und bin schon von Makros am meisten beeindruckt erlaubte mir komplexes Loop-entrolling Zur Kompilierungszeit kann ich das in keiner anderen Sprache, die ich kenne, elegant machen (d. h. Code-Generierung unter Beibehaltung der ursprünglichen Struktur).

Zur Optimierung: Ich habe Typ-Annotationen (viele "die Fixnum") in den gleichen Code gestreut. Sobald ich 3 oder 4 von ihnen hinzugefügt habe, merkte ich, dass ich es falsch gemacht habe - dafür sind Makros gedacht, wiederhole dich nicht selbst ...

; 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))))

... und in der Tat, das hat funktioniert: Anstatt viel "(das fixnum (...))" überall zu schreiben, gebe ich dem Ausdruck schnell "schnell" voran - und alles ist gut.

Aber dann...

Mir wurde klar, dass auch hier nicht alles aufhören sollte: Prinzipiell sollte das Makro "schnell" ... an der Spitze der Bewertung genannt werden, in diesem Fall:

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

... und es sollte rekursiv "pflanzen" (die fixnum (...)) in allen Unterausdrücken.

Kann das gemacht werden? Kann ein "defmacro" rekursiv sein?

AKTUALISIEREN: Ich hatte einige wirklich seltsame Probleme mit dem Versuch, dies zu tun, also endete ich damit, was Rord unten vorgeschlagen hat - d. H., Implementierte eine Funktion, testete sie im repl und rief sie aus dem Makro an:

(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)))

Und es funktioniert gut unter 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

Alles ist gut, bis auf einen Nebeneffekt: CMUCL funktioniert, aber kompiliert den Code nicht mehr:

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

Naja :-)

AKTUALISIEREN: Der Kompilierungsfehler wurde behoben und behoben eine andere SO Frage.


7
2017-11-13 22:02


Ursprung


Antworten:


Ein Makro wird nicht nur aufgerufen, sondern auch erweitert, wenn es verwendet wird. Daher kann die Bezugnahme auf ein Makro in seiner eigenen Definition unordentlich werden. Aber Sie müssen das nicht tun: Makros können reguläre Funktionen aufrufen, so dass Sie eine reguläre Funktion schreiben können, um die rekursive Listenverarbeitung auszuführen, und dann einfach die aus dem Makro verwenden.


5
2017-11-14 23:29



Sie können definitiv ein Makro schreiben, das sich in seiner eigenen Erweiterung verwendet. Dies ist absolut sinnvoll, und es ist ein natürlicher Weg, um das COND-Makro zu schreiben, das aufgrund der beliebig langen Liste von cond-Paaren eine reguläre, expandierende Struktur hat, die durch die übliche car / cdr- oder first / rest-Rekursion ausgedrückt werden kann.

(defmacro new-cond (&rest cond-pairs)
  (cond      ;; you need cond to compile new-cond syntax, LOL!
    ((null cond-pairs) nil)
    ((atom cond-pairs) (error "new-cond: bad syntax!"))
    (t `(if ,(first (first cond-pairs))
           (progn ,@(rest (first cond-pairs)))
           (new-cond ,@(rest cond-pairs))))))


> (macroexpand-1 '(new-cond (1 2) (3 4)))

(IF 1 (PROGN 2) (NEW-COND (3 4)))

Dies ist sehr analog zur Lazy-List-Verarbeitung. macroexpand expandiert nur das äußere Makro und hinterlässt ein "Versprechen" -Objekt, um die Erweiterung fortzusetzen. Dieses "Versprechen" -Objekt ist der Makroaufruf (NEW-COND (3 4)) am Schwanz.

Natürlich wird der echte Makroexpander die ganze Form durchlaufen und alle diese Versprechen (nicht erweiterte Makroaufrufe) "erzwingen", bis nichts mehr übrig ist.

Ich denke, es gibt einige Vorteile in diesem Stil, wie zum Beispiel einfaches Debuggen mit macroexpand. Du bekommst eine kleine Erweiterung. Wenn seine rekursive Natur offensichtlich ist, ist das ein Gewinn. Wenn das Makro so ist, dass dein Gehirn das Ganze erweitert sehen muss, ist es ein Verlust.

Hier kann ich das sehen (IF 1 (PROGN 2) (NEW-COND (3 4))) ist das korrekte Kompilieren. Wenn 1 wahr ist, evaluiere die Formularliste (2). Ansonsten mach weiter mit den anderen Kondompaaren. Jetzt müssen wir den Ein-Paar-Fall verifizieren:

> (macroexpand-1 '(new-cond (3 4)))
(IF 3 (PROGN 4) (NEW-COND))

Ausgezeichnet und der No-Pair-Fall (NEW-COND), durch augenscheinliche Inspektion, reduziert sich auf Null.


6
2018-03-22 08:11



Sicher. Sie können ein Makro rekursiv schreiben Spaziergänge Das Argument formt und transformiert sie so, wie Sie es wollen, eine Unterform nach der anderen. Da dies etwas komplizierter ist als es klingt, ist der richtige Weg, um eine Bibliothek zu verwenden Code gehen.

Quicklisp beinhaltet hu.dwim.walker, das ist anscheinend eine verbesserte Version der Arnesi Code-Geher.


5
2017-11-13 22:18