Matts Erklärung ist vollkommen in Ordnung - und er versucht es mit einem Vergleich mit C und Java, was ich nicht tun werde - aber aus irgendeinem Grund genieße ich es wirklich, ab und zu genau dieses Thema zu diskutieren. Hier ist meine Aufnahme bei einer Antwort.
Zu den Punkten (3) und (4):
Die Punkte (3) und (4) auf Ihrer Liste scheinen die interessantesten und derzeit noch relevantesten zu sein.
Um sie zu verstehen, ist es hilfreich, ein klares Bild davon zu haben, was mit Lisp-Code geschieht - in Form eines vom Programmierer eingegebenen Zeichenstroms - auf dem Weg zur Ausführung. Verwenden wir ein konkretes Beispiel:
;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])
;; this is the interesting bit:
(println (str/replace-re #"\d+" "FOO" "a123b4c56"))
Dieser Ausschnitt des Clojure- Codes wird ausgedruckt aFOObFOOcFOO
. Beachten Sie, dass Clojure den vierten Punkt auf Ihrer Liste möglicherweise nicht vollständig erfüllt, da die Lesezeit für Benutzercode nicht wirklich offen ist. Ich werde jedoch diskutieren, was es bedeuten würde, wenn dies anders wäre.
Nehmen wir also an, wir haben diesen Code irgendwo in einer Datei und bitten Clojure, ihn auszuführen. Nehmen wir außerdem an (der Einfachheit halber), dass wir es nach dem Bibliotheksimport geschafft haben. Das interessante Stück beginnt (println
und endet )
ganz rechts. Dies wird wie erwartet lexiert / analysiert, aber es ergibt sich bereits ein wichtiger Punkt: Das Ergebnis ist keine spezielle compilerspezifische AST-Darstellung - es handelt sich lediglich um eine reguläre Clojure / Lisp-Datenstruktur , nämlich eine verschachtelte Liste mit einer Reihe von Symbolen. Zeichenfolgen und - in diesem Fall - ein einzelnes kompiliertes Regex-Musterobjekt, das dem entspricht#"\d+"
wörtlich (mehr dazu weiter unten). Einige Lisps fügen diesem Prozess ihre eigenen kleinen Wendungen hinzu, aber Paul Graham bezog sich hauptsächlich auf Common Lisp. In den für Ihre Frage relevanten Punkten ähnelt Clojure CL.
Die ganze Sprache zur Kompilierungszeit:
Nach diesem Punkt befasst sich der Compiler nur noch mit Lisp-Datenstrukturen (an die Lisp-Programmierer gewöhnt sind) (dies gilt auch für einen Lisp-Interpreter; Clojure-Code wird immer kompiliert). An dieser Stelle wird eine wunderbare Möglichkeit offensichtlich: Warum nicht Lisp-Programmierern erlauben, Lisp-Funktionen zu schreiben, die Lisp-Daten manipulieren, die Lisp-Programme darstellen, und transformierte Daten ausgeben, die transformierte Programme darstellen, die anstelle der Originale verwendet werden sollen? Mit anderen Worten - warum nicht Lisp-Programmierern erlauben, ihre Funktionen als Compiler-Plugins zu registrieren, die in Lisp als Makros bezeichnet werden? Und tatsächlich hat jedes anständige Lisp-System diese Kapazität.
Makros sind also reguläre Lisp-Funktionen, die zur Kompilierungszeit vor der letzten Kompilierungsphase, in der der eigentliche Objektcode ausgegeben wird, mit der Darstellung des Programms arbeiten. Da die Art der Codemakros, die ausgeführt werden dürfen, unbegrenzt ist (insbesondere wird der von ihnen ausgeführte Code häufig selbst unter großzügiger Verwendung der Makrofunktion geschrieben), kann man sagen, dass "die gesamte Sprache zur Kompilierungszeit verfügbar ist ".
Die ganze Sprache zur Lesezeit:
#"\d+"
Kehren wir zu diesem Regex-Literal zurück. Wie oben erwähnt, wird dies zum Zeitpunkt des Lesens in ein tatsächlich kompiliertes Musterobjekt umgewandelt, bevor der Compiler die erste Erwähnung von neuem Code hört, der für die Kompilierung vorbereitet wird. Wie kommt es dazu?
Nun, die Art und Weise, wie Clojure derzeit implementiert ist, ist etwas anders als das, was Paul Graham im Sinn hatte, obwohl mit einem cleveren Hack alles möglich ist . In Common Lisp wäre die Geschichte konzeptionell etwas sauberer. Die Grundlagen sind jedoch ähnlich: Der Lisp Reader ist eine Zustandsmaschine, die nicht nur Zustandsübergänge durchführt und schließlich erklärt, ob sie einen "akzeptierenden Zustand" erreicht hat, sondern auch Lisp-Datenstrukturen ausspuckt, die die Zeichen darstellen. So werden die Zeichen 123
zur Zahl 123
usw. Der wichtige Punkt kommt jetzt: Diese Zustandsmaschine kann durch Benutzercode geändert werden. (Wie bereits erwähnt, ist dies in CLs Fall völlig richtig. Für Clojure ist ein Hack erforderlich (entmutigt und in der Praxis nicht verwendet). Aber ich schweife ab, es ist der Artikel von PG, auf den ich näher eingehen soll, also ...)
Wenn Sie also ein Common Lisp-Programmierer sind und die Idee von Vektorliteralen im Clojure-Stil mögen, können Sie einfach eine Funktion in den Reader einstecken, um angemessen auf eine Zeichenfolge zu reagieren - [
oder #[
möglicherweise - und sie als zu behandeln der Beginn eines Vektorliteral, der beim Abgleich endet ]
. Eine solche Funktion wird als Lesemakro bezeichnet und kann wie ein normales Makro jede Art von Lisp-Code ausführen, einschließlich Code, der selbst mit funky Notation geschrieben wurde, die durch zuvor registrierte Lesermakros aktiviert wurde. Es gibt also die gesamte Sprache zum Zeitpunkt des Lesens für Sie.
Verpacken:
Tatsächlich wurde bisher gezeigt, dass man reguläre Lisp-Funktionen zur Lese- oder Kompilierungszeit ausführen kann; Der einzige Schritt, den man von hier aus unternehmen muss, um zu verstehen, wie Lesen und Kompilieren selbst zum Lesen, Kompilieren oder Ausführen möglich sind, besteht darin, zu erkennen, dass Lesen und Kompilieren selbst von Lisp-Funktionen ausgeführt werden. Sie können einfach Lisp-Daten aus Zeichenströmen aufrufen read
oder eval
jederzeit einlesen oder Lisp-Code kompilieren bzw. ausführen. Das ist die ganze Sprache genau dort, die ganze Zeit.
Beachten Sie, dass die Tatsache, dass Lisp Punkt (3) aus Ihrer Liste erfüllt, für die Art und Weise, wie es Punkt (4) erfüllt, wesentlich ist. Die besondere Art der von Lisp bereitgestellten Makros hängt stark davon ab, dass Code durch reguläre Lisp-Daten dargestellt wird. Das ist etwas, was durch (3) ermöglicht wird. Übrigens ist hier nur der "baumartige" Aspekt des Codes wirklich entscheidend - Sie könnten möglicherweise ein Lisp mit XML schreiben lassen.