2017-04-03 2 views
2

Am Ende von Ch 8 in Practical Common Lisp präsentiert Peter Seibel das Makro once-only. Sein Zweck besteht darin, eine Reihe von subtilen Problemen mit der Variablenbewertung in benutzerdefinierten Makros zu mildern. Hinweis bin ich an dieser Stelle nicht versuchen zu verstehen, wie dieses Makro funktioniert, wie in einigen anderen Beiträgen, sondern nur, wie man sie richtig benutzt:Verwenden des "Once-only" -Makros

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for n in names collect (gensym)))) 
    `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 
     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 
     ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 
      ,@body))))) 

Das Folgende ist eine Probe (falsche) erfundene Makro, der versucht, weisen mehrere Probleme der Variablenbewertung auf. Sie gibt von einigen Delta eine Reihe von ganzen Zahlen iterieren, um den Bereich der Rückkehr:

(defmacro do-range ((var start stop delta) &body body) 
    "Sample macro with faulty variable evaluations." 
    `(do ((,var ,start (+ ,var ,delta)) 
     (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
    ,@body)) 

Zum Beispiel (do-range (i 1 15 3) (format t "~A " i))1 4 7 10 13 gedruckt werden soll und dann 14 zurückzukehren.

Die Probleme umfassen 1) Potentialerfassung des zweiten Auftreten von limit, da es als eine freie Variable auftritt, 2) Potentialerfassung des anfänglichen Auftretens der gebundenen Variablen limit, da es zusammen mit anderen Variablen, die in einem Ausdruck auftritt erscheint in den Makroparametern, 3) Out-of-Order-Auswertung, da delta vor stop ausgewertet wird, obwohl in der Parameterliste stop vor delta erscheint, und 4) mehrfache Variablenauswertungen, da stop und start mehrfach ausgewertet werden. Wie ich es verstehe, sollte once-only diese Probleme beheben:

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta limit) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

jedoch (macroexpand '(do-range (i 1 15 3) (format t "~A " i))) beschwert sich über limit eine ungebundene Variable ist. Wenn ich stattdessen auf with-gensyms umschalte, was nur für Probleme sorgen soll, läuft die Erweiterung ohne Zwischenfälle ab.

Ist dies ein Problem mit dem Makro once-only? Und löst once-only wirklich alle oben genannten Probleme (und vielleicht andere)?

+1

Warum haben Sie 'limit' überhaupt ? Könnten Sie nicht alle Anwendungen mit ', stop' in der 'Once-Only'-Version ersetzen? – melpomene

+0

@melpomene Ja, du hast Recht. Aber ich konnte mir keinen besseren, einfachen Weg vorstellen, um die zwei grundlegenden Arten von Variablenerfassungsproblemen in das fehlerhafte Makro einzuführen (aufgelistet als Probleme 1 und 2 oben). Irgendwelche anderen Ideen? – davypough

Antwort

5

Die ONCE-ONLY Makro

zu befreien eine Warnung zu erhalten, dass N ungenutzt ist, würde ich das Makro ändern:

(defmacro once-only ((&rest names) &body body) 
    (let ((gensyms (loop for nil in names collect (gensym)))) 
          ; changed N to NIL, NIL is ignored 
    `(let (,@(loop for g in gensyms collect `(,g (gensym)))) 
     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n))) 
     ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g))) 
      ,@body))))) 

Der Zweck dieses Makro, um sicherzustellen, dass Ausdrücke werden nur einmal und in einer definierten Reihenfolge ausgewertet. Dazu wird es neue unbemannte Variablen einführen und die Bewertungsergebnisse an diese binden. Innerhalb des Makros sind die neuen Variablen verfügbar. Das Makro selbst ist vorgesehen, um das Schreiben von Makros zu erleichtern.

Mit nur einmal in DO-RANGE

Ihr Beispiel Verwendung von ONCE-ONLY:

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta limit) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

Warum gibt es LIMIT in der once-only Liste? limit ist dort nicht definiert. LIMIT wird innerhalb des Formulars ONCE-ONLY als Symbol verwendet, aber außerhalb gibt es keine Bindung.

ONCE-ONLY erwartet, dass die Liste der Namen eine Liste von Symbolen ist und dass diese Namen an Formulare gebunden sind. In Ihrem Fall ist limit ein Symbol, aber es ist nicht definiert.

Wir brauchen limit aus der Liste der Namen zu entfernen:

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta)) 
      (limit ,stop)) 
     ((> ,var limit) (- ,stop ,start)) 
     ,@body))) 

Nun, was LIMIT zu tun? Da once-only Bindungen für die Namen, einschließlich für STOP, können wir das Symbol LIMIT und ersetzen ihre Verwendung mit ,stop beseitigen:

(defmacro do-range ((var start stop delta) &body body) 
    (once-only (start stop delta) 
    `(do ((,var ,start (+ ,var ,delta))) 
     ((> ,var ,stop) (- ,stop ,start)) 
     ,@body))) 

Beispiel:

CL-USER 137 > (pprint 
       (macroexpand 
       '(do-range (i 4 10 2) 
        (print i)))) 

(LET ((#1=#:G2170 4) 
     (#3=#:G2171 10) 
     (#2=#:G2172 2)) 
    (DO ((I #1# (+ I #2#))) 
     ((> I #3#) (- #3# #1#)) 
    (PRINT I))) 
+0

Funktioniert perfekt, großartig! Aber um dies zu verdeutlichen, ist es nicht erforderlich, "Limit" von "Do" zu entfernen. Entfernen Sie es einfach aus den 'once-only'-Variablen und das ursprüngliche fehlerhafte Makro funktioniert dann. – davypough

+0

@davypough: aber dann ist LIMIT im Körper sichtbar, möglicherweise auch Schatten eines anderen LIMIT. Das ist etwas, was wir normalerweise vermeiden wollen. –

Verwandte Themen