7

Ich habe ein wenig Probleme zu verstehen, was aus gelesenen Makros beim Kompilieren einer Datei von Lisp-Code in einen Bytecode oder RAW-Assembly (oder eine Fasl-Datei für diese Angelegenheit). Oder vielleicht verstehe ich es, weiß es aber nicht. Ich bin nur wirklich verwirrt.Kompilieren Lisp-Code mit lesen Makros

Wenn Sie ein Lesemakro verwenden, müssen Sie die Quelle nicht verfügbar haben?

Wenn Sie dies tun, müssen Sie den Quellcode ausführen, der die Funktion des gelesenen Makros ausmacht. Wenn nicht, wie können sie dann arbeiten, wenn Sie Dinge wie read-char tun können?

, das zu tun, wenn Sie das lesen Makro Verwendung zuvor definierten Variablen haben wollen, müssen Sie alle der Code, bevor es zu der Ausführung, so wird dies der Laufzeit, die alles vermasselt.

Wenn Sie den Code nicht zuvor ausführen, ist das oben definierte Objekt nicht verfügbar.

Was ist mit Funktionen oder Compiler-Makros, die Lese-Makros definieren? Ich würde annehmen, dass sie überhaupt nicht arbeiten würden, außer Sie require oder load eine Datei oder etwas, das nicht kompiliert wird. Aber wenn sie kompiliert würden, könnten sie sie nicht benutzen?

Wenn einige meiner Annahmen richtig sind, bedeutet dies, dass es einen großen Unterschied gibt in "welche Daten für Makros verfügbar sind" und "Welche Makros für Funktionen verfügbar sind" abhängig davon, ob Sie eine ganze Datei kompilieren später ausgeführt werden oder eine Datei Zeile für Zeile interpretieren (also einen Ausdruck nach dem anderen lesen, kompilieren und auswerten).

Kurz gesagt, es scheint so zu sein, eine Zeile zu einem Formular zu kompilieren, wo es ohne weitere Makroverarbeitung oder was auch immer ausgeführt werden kann, müssen Sie die vorherigen Zeilen lesen, kompilieren und ausführen.

Denken Sie daran, wieder, dass diese Fragen gelten Lisp zu kompilieren, es nicht zu interpretieren, wo Sie jede Zeile ausgeführt werden können, wie es kommt.

Sorry für meine Wanderungen, aber ich bin neu lispeln und mehr wissen will, wie es funktioniert.

Antwort

1

Makros (einschließlich Lese-Makros) sind nichts anderes als Funktionen, und sie werden genauso behandelt wie alle anderen Funktionen. Sie müssen keinen Quellcode behalten, nachdem eine Funktion oder ein Makro kompiliert wurde.

Viele Lisp-Implementierungen würden überhaupt keine Interpretation vornehmen. Zum Beispiel kompiliert SBCL standardmäßig nur, ohne in einen Interpretationsmodus zu wechseln, selbst für eval. Eine wichtige Nuance ist, dass die Common-Lisp-Kompilierung inkrementell ist (im Gegensatz zu der separaten Kompilierung, die in vielen Scheme-Implementierungen und Sprachen wie C und Java üblich ist), die es Ihnen ermöglicht, eine Funktion oder ein Makro zu kompilieren und sofort zu verwenden. Kompilationseinheit ".

+0

tatsächlich, SBCL hat Interpretationsmodus, es ist nur standardmäßig deaktiviert: http://www.sbcl.org/manual/Interpreter.html –

+0

Common Lisp Compilierung ist inkrementell, aber die Zusammenstellung von Dateien ist etwas anders definiert. –

+0

@Rainer können Sie erarbeiten? Ich bin damit nicht vertraut –

5

Dies ist eigentlich eine interessante Frage, und etwas, mit dem viele anfängliche Lisp-Programmierer zu kämpfen haben. Einer der Hauptgründe dafür ist, dass alles größtenteils "wie erwartet" funktioniert und Sie erst wirklich anfangen, über diese Dinge nachzudenken, wenn Sie die erweiterten Funktionen von Lisp verwenden.

Die kurze Antwort auf Ihre Frage ist, ja, um den Code korrekt zu kompilieren, muss ein Teil des vorherigen Codes ausgeführt worden sein. Beachten Sie das Wort einige, das ist der Schlüssel. Lassen Sie uns ein kleines Beispiel geben.Betrachten wir eine Datei mit folgendem Inhalt:

(print 'a) 

(defmacro bar (x) `(print ,x)) 

(bar 'b) 

Wie Sie bereits herausgefunden haben, wenn Sie COMPILE-FILE auf dieser Datei ausführen, die resultierende .fasl Datei wird einfach die kompilierte Version des folgenden Codes enthalten:

(print 'a) 
(print 'b) 

"Aber", könnten Sie fragen, "Warum wurde das Formular DEFMACRO während der Kompilierung ausgeführt, aber das PRINT Formular war nicht?". Die Antwort wird im Hyperspec-Abschnitt 3.2.3 erläutert. Es enthält den folgenden Satz:

Normalerweise sind die Top-Level-Formulare in einer Datei mit Kompilierung Datei kompiliert erscheinen nur ausgewertet werden, wenn die resultierende kompilierte Datei ist geladen, und nicht, wenn die Datei kompiliert wird. Es ist jedoch in der Regel der Fall, dass einige Formulare in der Datei bei Kompilierung Zeit ausgewertet werden müssen, so dass der Rest der Datei korrekt gelesen und kompiliert werden kann.

Es gibt ein Formular, mit dem genau gesteuert werden kann, wann ein Formular ausgewertet wird. Sie verwenden EVAL-WHEN für diesen Zweck. Genau so implementiert der Lisp-Compiler DEFMACRO selbst. Sie können sehen, wie Ihre Lisp implementiert es durch den folgenden aus dem REPL eingeben:

(macroexpand '(defmacro bar (x) `(print ,x))) 

Offensichtlich verschiedene Lisp-Implementierungen dies anders umsetzen, aber der Schlüssel Wichtig ist, dass es die Definition in einer Form hüllt: (eval-when (:compile-toplevel :load-toplevel :execute) ...). Dies teilt dem Compiler mit, dass das Formular sowohl beim Kompilieren der Datei als auch beim Laden der Datei ausgewertet werden soll. Anderenfalls könnten Sie das Makro nicht in derselben Datei verwenden, in der es definiert wurde. Wenn das Formular nur beim Kompilieren der Datei ausgewertet wurde, können Sie das Makro nach dem Laden nicht in einer anderen Datei verwenden.

+2

Die kompilierte Datei enthält nicht nur die zwei print-Anweisungen, es enthält auch die Makrodefinition. –

+1

Das Ausführen eines Compiler-Makros sowohl zur Kompilierungszeit als auch zur Laufzeit entspricht ungefähr dem, was ich für Compiler-Makros erwartet habe. Jedoch liest diese nicht (die ich sehe) Makros. Compilermakros sind einfache Funktionen (sie nehmen reale Objekte als Parameter, so dass sie theoretisch die Expansion bis zur Laufzeit verzögern können), aber gelesene Makros hängen vom Text ab. Wie arbeiten Sie? –

+0

@SethCarnegie die Kompilierung ist inkrementell. Jedes Formular wird so verarbeitet, wie es gelesen wird (in diesem Zusammenhang verarbeitet bedeutet entweder übersetzt oder ausgewertet oder beides). Dies bedeutet, dass die erste Form das Verhalten des Lesers ändern kann, und dieses neue Verhalten wird dann nachfolgende Formulare beeinflussen, wenn sie gelesen werden. –

4

Erstellung von Dateien in Common Lisp definiert: CLHS Section 3.2.3 File Compilation

Beim Kompilieren: Verwendung einer Form zu machen, eine Lese Makro müssen Sie das Makro Implementierung an den Compiler zur Verfügung lesen machen.

Normalerweise werden solche Abhängigkeiten mit einer defsystem-Funktion behandelt, in der die Abhängigkeiten zwischen verschiedenen Dateien eines Systems (etwa eines Projekts) beschrieben werden. Um eine bestimmte Datei zu kompilieren, muss eine andere Datei (vorzugsweise die kompilierte Version) in die kompilierende Lisp geladen werden.

Wenn Sie jetzt das gelesene Makro definieren und Formulare verwenden möchten, die seine Notation in derselben Datei verwenden, müssen Sie erneut sicherstellen, dass der Compiler das gelesene Makro und seine Implementierung kennt. Der Dateicompiler verfügt über eine Kompilierungsumgebung. Es lädt die kompilierten Funktionen der gleichen Datei nicht standardmäßig in diese Umgebung.

Um den Compiler auf bestimmten Code in einer kompilierten Datei aufmerksam zu machen, stellt Common Lisp EVAL-WHEN bereit.

Blick Lassen Sie sich in einem las Makro Beispiel:

(set-syntax-from-char #\] #\)) 

(defun reader-example (stream char) 
    (declare (ignore char)) 
    (let ((class (read stream t nil t)) 
     (args (read-delimited-list #\] stream t))) 
    (apply #'make-instance 
      class 
      args))) 

(set-macro-character #\[ 'reader-example) 

(defclass example() 
    ((name :initarg :name))) 

(defvar *examples* 
    (list [example :name e1] 
     [example :name e2] 
     [example :name e3])) 

Wenn Sie die oben genannte Quelle laden, alles ist in Ordnung. Aber wenn wir einen Datei-Compiler verwenden, kompiliert er nicht, ohne ihn zuerst zu laden. Der Dateicompiler zum Beispiel wird aufgerufen, indem die Funktion COMPILE-FILE mit einem Pfadnamen aufgerufen wird.

Kompilieren Sie nun die Datei:

(set-syntax-from-char #\] #\)) 

oben wird bei der Kompilierung nicht ausgeführt werden. Die neue Syntaxänderung ist zur Kompilierzeit nicht verfügbar.

(defun reader-example (stream char) 
    (declare (ignore char)) 
    (let ((class (read stream t nil t)) 
     (args (read-delimited-list #\] stream t))) 
    (apply #'make-instance 
      class 
      args))) 

Die obige Funktion wird kompiliert, aber nicht geladen. Diese Implementierung steht dem Compiler in späteren Schritten nicht zur Verfügung.

(set-macro-character #\[ 'reader-example) 

Noch einmal, das obige Formular wird nicht ausgeführt - nur Code für es wird generiert.

(defclass example() 
    ((name :initarg :name))) 

Der Compiler notiert die Klasse, kann aber später keine Instanzen davon erstellen.

(defvar *examples* 
    (list [example :name e1] 
     [example :name e2] 
     [example :name e3])) 

Above Code löst einen Fehler, da die Lese Makro bei der Kompilierung nicht verfügbar ist - sofern sie nicht vorher geladen wurde.

Es gibt jetzt zwei einfache Lösungen:

  • setzten die Umsetzung des Lese Makro in einer separaten Datei und stellen Sie sicher, dass es kompiliert wird, und vor jeder Datei geladen, die die Lese Makro verwendet.

  • legte ein EVAL-WHEN um den Code, den Effekt bei der Kompilierung haben muss:

Beispiel:

(EVAL-WHEN (:compile-toplevel :load-toplevel :execute) 
    (do-something-also-at-compile-time)) 

Oberhalb vom Compiler zu sehen sein wird und auch dann ausgeführt. Jetzt müssen Sie sicherstellen, dass der Code alles hat, was er aufruft (alle benötigten Definitionen), während der Kompilierung.

Unnötig zu sagen: Es ist ein guter Stil, solche Kompilierungsabhängigkeiten so weit wie möglich zu reduzieren. Stellen Sie die benötigte Funktionalität in der Regel in eine separate Datei und stellen Sie sicher, dass diese Datei kompiliert und in das kompilierende Lisp geladen wird, bevor Sie eine Datei kompilieren, die sie verwendet.