2016-01-27 5 views
8

Ich lese Practical Common Lisp von Peter Seibel. In Chapter 9 wird er den Leser durch die Schaffung einer Einheit Test-Framework zu Fuß, und er enthält das folgende Makro, um zu bestimmen, ob eine Liste der einzig wahre Ausdrücke zusammen:Gibt es einen Vorteil für dieses Makro?

(defmacro combine-results (&body forms) 
    (let ((result (gensym))) 
    `(let ((,result t)) 
     ,@(loop for form in forms collect `(unless ,form (setf ,result nil))) 
     ,result))) 

Ich bin nicht klar, was den Vorteil der Verwendung von obwohl ein Makro ist hier - es scheint, wie die folgende wäre klarer, sowie effiziente für dynamische Werte:

(defun combine-results (&rest expressions) 
    (let ((result t)) 
    (loop for expression in expressions do (unless expression (setf result nil))) 
    result)) 

ist der Vorteil für den Makro nur, dass es zur Laufzeit für alle Anrufe effizienter ist, die zur Kompilierzeit erweitert? Oder ist es ein Paradigmen-Ding? Oder versucht das Buch nur Ausreden zu geben, um verschiedene Muster in Makros zu üben?

+2

Der eigentliche Vorteil des Makros besteht darin, dass es Zugriff auf die ursprünglichen Formulare hat und somit das Formular drucken kann, das nicht zu einem echten Wert ausgewertet werden konnte. Es ist traurig, dass das Buchbeispiel das nicht wirklich zeigt. – hans23

+1

@ hans23: Im Buchcode sind die Formulare eigentlich Makros, die das Ergebnis ausgeben. –

Antwort

7

Ihre Beobachtung ist grundsätzlich richtig; und in der Tat kann Ihre Funktion nur sein:

(defun combine-results (&rest expressions) 
    (every #'identity expressions)) ;; i.e. all expressions are true? 

Da das Makro bedingungslos alle seine Argumente auswertet von links nach rechts, und die Erträge T wenn sie alle wahr sind, ist es im Grunde nur Inline-Optimierung etwas, das kann durch eine Funktion erledigt werden. Funktionen können angefordert werden, um mit (declaim 'inline ...) inline zu sein. Außerdem können wir mit define-compiler-macro ein Compiler-Makro für die Funktion schreiben. Mit diesem Makro können wir die Erweiterung und erzeugen, die wir als eine Funktion haben, die wir apply und indirekt auf andere haben können.

Andere Möglichkeiten, das Ergebnis innerhalb der Funktion Berechnung:

(not (position nil expressions)) 
(not (member nil expressions)) 

Das Beispiel wie Makro Praxis sieht: eine gensym machen, und Generieren von Code mit loop. Das Makro ist auch der Ausgangspunkt für etwas, das in einem Unit-Test-Framework erscheinen könnte.

+1

Das Makro könnte nützlich sein (zum Beispiel für die Optimierung), wenn es halbwegs durch die Auswertung seiner Argumente kurzgeschlossen wurde, was der Funktionsansatz niemals zulässt. Aber in dieser Form ist es nicht. –

+2

@JoaoTavora Wenn das Makro kurzgeschlossen wäre, wäre es eine redundante Implementierung des Standard 'und' Makros! – Kaz

+0

@Kaz, nicht ganz, da es "t" zurückgeben würde, wenn alle Argumente nicht-null sind. Aber ja, grundsätzlich. Ich denke, der wirkliche Nutzen in diesem Fall ist der "Ausführlichkeits" -Winkel von @ hans23: Mit einem Makro können Sie Formulare drucken und alles, was Sie wollen, mit den Formularen selbst machen. –

9

In diesem Fall spielt es keine Rolle, aber für zukünftige Versionen könnte es sinnvoller sein, ein Makro zu verwenden. Macht es Sinn, ein Makro zu verwenden? Abhängig von dem Anwendungsfall:

eine Funktion Mit

(combine-results (foo) (bar) (baz)) 

Beachten Sie, dass zur Laufzeit Lisp sieht, dass combine-results eine Funktion ist. Es wertet dann die Argumente aus. Es ruft dann die Funktion combine-results mit den Ergebniswerten auf. Diese Bewertungsregel ist in Common Lisp fest codiert.

Dies bedeutet: Der Code der Funktion läuft nach die Argumente wurden ausgewertet.

mit einem Makro

(combine-results (foo) (bar) (baz)) 

Da Lisp sieht, dass es ein Makro ist, ruft es das Makro bei Makroexpansionszeit und generiert Code. Was der generierte Code ist, hängt vollständig vom Makro ab. Was dies erlaubt, ist, einen Code wie diesen zu erzeugen:

(prepare-an-environment 

    (embed-it (foo)) 
    (embed-it (bar)) 
    (embed-it (baz)) 

    (do-post-processing)) 

Dieser Code wird dann ausgeführt.So könnten Sie beispielsweise Systemvariablen einrichten, Fehlerhandler bereitstellen, eine Berichtsmaschine einrichten usw. Jedes einzelne Formular könnte dann auch in eine andere Form eingebettet werden. Und nachdem die Funktionen ausführen, könnte man einige tun aufzuräumen, Reporting etc. prepare-an-environment und embed-it würde Makros oder speziellen Operatoren sein, die sicherstellen, dass einige Code ausgeführt vor, um und nach die eingebetteten Formen wir stellten zur Verfügung.

würde Wir haben Code vor, um und nach die hierfür vorgesehenen Formularen ausgeführt wird. Ist das nützlich? Es könnte sein. Es könnte für ein umfangreicheres Test-Framework nützlich sein.

Wenn das bekannt vorkommt, dann würde man sehen, dass man mit CLOS-Methoden (primär, vor, nach, herum) eine ähnliche Codestruktur bekommen kann. Die Tests würden in einer primären Methode ausgeführt und der andere Code würde als um, vor und nach Methoden ausgeführt werden.

Beachten Sie, dass ein Makro auch drucken kann (siehe Kommentar von Hans23), überprüfen und/oder ändern Sie die bereitgestellten Formulare.