Wenn man Paul Grahams Essays über Programmiersprachen liest, würde man denken, dass Lisp-Makros der einzige Weg sind. Als vielbeschäftigter Entwickler, der auf anderen Plattformen arbeitet, hatte ich nicht das Privileg, Lisp-Makros zu verwenden. Erklären Sie als jemand, der die Begeisterung verstehen möchte, was diese Funktion so leistungsfähig macht.
Bitte beziehen Sie dies auch auf etwas, das ich aus den Welten der Python-, Java-, C # - oder C-Entwicklung verstehen würde.
macros
lisp
homoiconicity
Minze
quelle
quelle
Antworten:
Um die kurze Antwort zu geben, werden Makros zum Definieren von Sprachsyntaxerweiterungen für Common Lisp oder Domain Specific Languages (DSLs) verwendet. Diese Sprachen sind direkt in den vorhandenen Lisp-Code eingebettet. Jetzt können die DSLs eine ähnliche Syntax wie Lisp (wie Peter Norvigs Prolog Interpreter für Common Lisp) oder eine völlig andere (z. B. Infix Notation Math für Clojure) haben.
Hier ist ein konkreteres Beispiel: In
Python sind Listenverständnisse in die Sprache integriert. Dies gibt eine einfache Syntax für einen häufigen Fall. Die Linie
liefert eine Liste mit allen geraden Zahlen zwischen 0 und 9. In den 1,5 Tagen von Python gab es keine solche Syntax. Sie würden so etwas eher verwenden:
Diese sind beide funktional gleichwertig. Lassen Sie uns unsere Aufhebung des Unglaubens aufrufen und so tun, als hätte Lisp ein sehr begrenztes Schleifenmakro, das nur Iteration ausführt und keine einfache Möglichkeit bietet, das Äquivalent von Listenverständnissen zu erreichen.
In Lisp könnte man folgendes schreiben. Ich sollte beachten, dass dieses erfundene Beispiel so ausgewählt ist, dass es mit dem Python-Code identisch ist, kein gutes Beispiel für Lisp-Code.
Bevor ich weiter gehe, sollte ich besser erklären, was ein Makro ist. Es ist eine Transformation, die Code für Code ausgeführt wird. Das heißt, ein Code, der vom Interpreter (oder Compiler) gelesen wird und Code als Argument verwendet, manipuliert und gibt das Ergebnis zurück, das dann direkt ausgeführt wird.
Das ist natürlich viel Tippen und Programmierer sind faul. So könnten wir DSL für das Listenverständnis definieren. Tatsächlich verwenden wir bereits ein Makro (das Schleifenmakro).
Lisp definiert einige spezielle Syntaxformen. Das Zitat (
'
) gibt an, dass das nächste Token ein Literal ist. Das Quasiquote oder Backtick (`
) zeigt an, dass das nächste Token ein Literal mit Escapezeichen ist. Escapezeichen werden vom Komma-Operator angezeigt. Das Literal'(1 2 3)
entspricht dem von Python[1, 2, 3]
. Sie können es einer anderen Variablen zuweisen oder an Ort und Stelle verwenden. Sie können sich`(1 2 ,x)
das Äquivalent zu Pythons vorstellen,[1, 2, x]
bei demx
es sich um eine zuvor definierte Variable handelt. Diese Listennotation ist Teil der Magie, die in Makros steckt. Der zweite Teil ist der Lisp-Reader, der den Code auf intelligente Weise durch Makros ersetzt.So können wir ein Makro namens
lcomp
(kurz für Listenverständnis) definieren. Die Syntax entspricht genau der Python, die wir im Beispiel verwendet haben[x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
Jetzt können wir über die Kommandozeile ausführen:
Ziemlich ordentlich, oder? Jetzt hört es hier nicht auf. Sie haben einen Mechanismus oder einen Pinsel, wenn Sie möchten. Sie können jede gewünschte Syntax verwenden. Wie die
with
Syntax von Python oder C # . Oder die LINQ-Syntax von .NET. Dies ist es, was die Menschen zu Lisp anzieht - ultimative Flexibilität.quelle
(loop for x from 0 below 10 when (evenp x) collect x)
, weitere Beispiele hier . Aber in der Tat ist Schleife "nur ein Makro" (ich habe es vor einiger Zeit von Grund auf neu implementiert )Eine umfassende Debatte rund um das Lisp-Makro finden Sie hier .
Eine interessante Untergruppe dieses Artikels:
quelle
Gängige Lisp-Makros erweitern im Wesentlichen die "syntaktischen Grundelemente" Ihres Codes.
In C funktioniert das switch / case-Konstrukt beispielsweise nur mit integralen Typen. Wenn Sie es für Floats oder Strings verwenden möchten, bleiben verschachtelte if-Anweisungen und explizite Vergleiche übrig. Es gibt auch keine Möglichkeit, ein C-Makro zu schreiben, um die Arbeit für Sie zu erledigen.
Da ein Lisp-Makro (im Wesentlichen) ein Lisp-Programm ist, das Codefragmente als Eingabe verwendet und Code zurückgibt, um den "Aufruf" des Makros zu ersetzen, können Sie Ihr Repertoire an "Primitiven" so weit erweitern, wie Sie möchten mit einem besser lesbaren Programm.
Um dasselbe in C zu tun, müssten Sie einen benutzerdefinierten Vorprozessor schreiben, der Ihre ursprüngliche (nicht ganz C) Quelle frisst und etwas ausspuckt, das ein C-Compiler verstehen kann. Es ist kein falscher Weg, aber es ist nicht unbedingt der einfachste.
quelle
Mit Lisp-Makros können Sie entscheiden, wann (wenn überhaupt) ein Teil oder Ausdruck ausgewertet wird. Um ein einfaches Beispiel zu nennen, denken Sie an Cs:
Was dies sagt, ist: Bewerten
expr1
und, falls es wahr sein sollte, bewertenexpr2
usw.Versuchen Sie nun, daraus
&&
eine Funktion zu machen ... das ist richtig, das können Sie nicht. So etwas nennen wie:Wird alle drei bewerten,
exprs
bevor eine Antwort gegeben wird, unabhängig davon, ob sieexpr1
falsch war!Mit Lisp-Makros können Sie Folgendes codieren:
Jetzt haben Sie eine
&&
, die Sie wie eine Funktion aufrufen können und die keine Formulare auswertet, die Sie an sie übergeben, es sei denn, sie sind alle wahr.Um zu sehen, wie nützlich dies ist, kontrastieren Sie:
und:
Andere Dinge, die Sie mit Makros tun können, sind das Erstellen neuer Schlüsselwörter und / oder Minisprachen (siehe
(loop ...)
Makro für ein Beispiel) und das Integrieren anderer Sprachen in lisp. Sie können beispielsweise ein Makro schreiben, mit dem Sie Folgendes sagen können:Und das geht nicht einmal in Reader-Makros .
Hoffe das hilft.
quelle
(and ...
wird Ausdrücke auswerten, bis man als falsch bewertet, beachten Sie, dass Nebenwirkungen, die durch die falsche Auswertung erzeugt werden, auftreten, nur die nachfolgenden Ausdrücke werden übersprungen.Ich glaube nicht, dass ich jemals gesehen habe, wie Lisp-Makros besser erklärt wurden als von diesem Kollegen: http://www.defmacro.org/ramblings/lisp.html
quelle
Überlegen Sie, was Sie in C oder C ++ mit Makros und Vorlagen tun können. Sie sind sehr nützliche Tools zum Verwalten von sich wiederholendem Code, aber sie sind in schwerwiegender Weise eingeschränkt.
Lisp- und Lisp-Makros lösen diese Probleme.
Sprechen Sie mit allen, die C ++ beherrschen, und fragen Sie sie, wie lange sie damit verbracht haben, die gesamte Fudgery für Vorlagen zu erlernen, die sie für die Metaprogrammierung von Vorlagen benötigen. Oder all die verrückten Tricks in (ausgezeichneten) Büchern wie Modern C ++ Design , die immer noch schwer zu debuggen und (in der Praxis) zwischen realen Compilern nicht portierbar sind, obwohl die Sprache seit einem Jahrzehnt standardisiert ist. All das schmilzt dahin, wenn die Sprache, die Sie für die Metaprogrammierung verwenden, dieselbe Sprache ist, die Sie für die Programmierung verwenden!
quelle
Ein lisp-Makro nimmt ein Programmfragment als Eingabe. Dieses Programmfragment stellt eine Datenstruktur dar, die beliebig bearbeitet und transformiert werden kann. Am Ende gibt das Makro ein weiteres Programmfragment aus, und dieses Fragment wird zur Laufzeit ausgeführt.
C # verfügt nicht über eine Makrofunktion. Eine Entsprechung wäre jedoch, wenn der Compiler den Code in einen CodeDOM-Baum analysieren und an eine Methode übergeben würde, die diesen in ein anderes CodeDOM umwandelt, das dann in IL kompiliert wird.
Dies könnte verwendet werden, um "Zucker"
for each
-Syntax wie die -statement-using
Klausel, linq -expressions usw.select
als Makros zu implementieren , die sich in den zugrunde liegenden Code umwandeln.Wenn Java Makros hätte, könnten Sie die Linq-Syntax in Java implementieren, ohne dass Sun die Basissprache ändern muss.
Hier ist ein Pseudocode, wie ein Makro im Lisp-Stil in C # zur Implementierung
using
aussehen könnte:quelle
using
würde wie folgt aussehen ;)Da die vorhandenen Antworten gute konkrete Beispiele geben, die erklären, was und wie Makros erreichen, wäre es vielleicht hilfreich, einige der Gedanken darüber zusammenzufassen, warum die Makrofunktion im Vergleich zu anderen Sprachen einen erheblichen Gewinn darstellt . zuerst aus diesen Antworten, dann aus einer anderen:
- Vatine
- Matt Curtis
- Miguel Ping
- Peter Seibel in "Practical Common Lisp"
quelle
Ich bin mir nicht sicher, ob ich jedem (ausgezeichneten) Beitrag einen Einblick geben kann, aber ...
Lisp-Makros funktionieren aufgrund der Lisp-Syntax hervorragend.
Lisp ist eine sehr normale Sprache (denken Sie an alles ist eine Liste ); Mit Makros können Sie Daten und Code gleich behandeln (zum Ändern von Lisp-Ausdrücken sind keine String-Analyse oder andere Hacks erforderlich). Sie kombinieren diese beiden Funktionen und haben eine sehr saubere Möglichkeit, Code zu ändern.
Bearbeiten: Was ich sagen wollte ist, dass Lisp homoikonisch ist , was bedeutet, dass die Datenstruktur für ein Lisp-Programm in Lisp selbst geschrieben ist.
Am Ende haben Sie also die Möglichkeit, einen eigenen Codegenerator über der Sprache zu erstellen, indem Sie die Sprache selbst mit all ihrer Kraft verwenden (z. B. müssen Sie sich in Java mit dem Bytecode-Weben hacken, obwohl einige Frameworks wie AspectJ dies zulassen Tun Sie dies mit einem anderen Ansatz, es ist im Grunde ein Hack).
In der Praxis bauen Sie mit Makros Ihre eigene Minisprache auf lisp auf, ohne zusätzliche Sprachen oder Werkzeuge lernen zu müssen und die volle Kraft der Sprache selbst zu nutzen.
quelle
S-expressions
nicht Listen.Lisp-Makros stellen ein Muster dar, das in fast jedem größeren Programmierprojekt vorkommt. Schließlich haben Sie in einem großen Programm einen bestimmten Codeabschnitt, in dem Sie erkennen, dass es für Sie einfacher und weniger fehleranfällig wäre, ein Programm zu schreiben, das Quellcode als Text ausgibt, den Sie dann einfach einfügen können.
In Python haben Objekte zwei Methoden
__repr__
und__str__
.__str__
ist einfach die vom Menschen lesbare Darstellung.__repr__
Gibt eine Darstellung zurück, die gültiger Python-Code ist, dh etwas, das als gültiger Python in den Interpreter eingegeben werden kann. Auf diese Weise können Sie kleine Python-Schnipsel erstellen, die gültigen Code generieren, der in Ihre eigentliche Quelle eingefügt werden kann.In Lisp wurde dieser gesamte Prozess vom Makrosystem formalisiert. Sicher, es ermöglicht Ihnen, Erweiterungen der Syntax zu erstellen und alle möglichen ausgefallenen Dinge zu tun, aber die tatsächliche Nützlichkeit wird durch die obigen Ausführungen zusammengefasst. Natürlich hilft es, dass Sie mit dem Lisp-Makrosystem diese "Schnipsel" mit der vollen Leistung der gesamten Sprache bearbeiten können.
quelle
Kurz gesagt, Makros sind Transformationen von Code. Sie ermöglichen die Einführung vieler neuer Syntaxkonstrukte. Betrachten Sie beispielsweise LINQ in C #. In lisp gibt es ähnliche Spracherweiterungen, die von Makros implementiert werden (z. B. eingebautes Schleifenkonstrukt, Iteration). Makros verringern die Codeduplizierung erheblich. Makros ermöglichen das Einbetten von «kleinen Sprachen» (z. B. wenn in c # / java XML zum Konfigurieren verwendet wird, kann in lisp dasselbe mit Makros erreicht werden). Makros können Schwierigkeiten bei der Verwendung von Bibliotheken verbergen.
ZB kann man in lisp schreiben
Dadurch werden alle Datenbankdaten (Transaktionen, ordnungsgemäßes Schließen der Verbindung, Abrufen von Daten usw.) ausgeblendet, während in C # SqlConnections, SqlCommands erstellt, SqlParameters zu SqlCommands hinzugefügt, SqlDataReaders wiederholt und ordnungsgemäß geschlossen werden müssen.
quelle
Während das vor allem erklärt, was Makros sind und sogar coole Beispiele haben, denke ich, dass der Hauptunterschied zwischen einem Makro und einer normalen Funktion darin besteht, dass LISP alle Parameter zuerst auswertet, bevor die Funktion aufgerufen wird. Bei einem Makro ist es umgekehrt. LISP übergibt die nicht bewerteten Parameter an das Makro. Wenn Sie beispielsweise (+ 1 2) an eine Funktion übergeben, erhält die Funktion den Wert 3. Wenn Sie dies an ein Makro übergeben, erhält sie eine Liste (+ 1 2). Dies kann verwendet werden, um alle Arten von unglaublich nützlichen Dingen zu erledigen.
Messen Sie die Zeit, die zum Ausführen einer übergebenen Funktion benötigt wird. Bei einer Funktion wird der Parameter ausgewertet, bevor die Steuerung an die Funktion übergeben wird. Mit dem Makro können Sie Ihren Code zwischen Start und Stopp Ihrer Stoppuhr verbinden. Das Folgende hat genau den gleichen Code in einem Makro und einer Funktion und die Ausgabe ist sehr unterschiedlich. Hinweis: Dies ist ein erfundenes Beispiel, und die Implementierung wurde so ausgewählt, dass sie identisch ist, um den Unterschied besser hervorzuheben.
quelle
Ich habe dies aus dem allgemeinen Lisp-Kochbuch erhalten, aber ich denke, es hat erklärt, warum Lisp-Makros auf nette Weise gut sind.
"Ein Makro ist ein gewöhnlicher Teil des Lisp-Codes, der mit einem anderen Teil des mutmaßlichen Lisp-Codes arbeitet und ihn in eine ausführbare Datei (eine Version, die der ausführbaren Version näher kommt) übersetzt. Das mag etwas kompliziert klingen. Geben wir also ein einfaches Beispiel. Angenommen, Sie möchten eine Version von setq, die zwei Variablen auf den gleichen Wert setzt. Wenn Sie also schreiben
wenn
z=8
sowohl x als auch y auf 11 gesetzt sind. (Ich kann mir keine Verwendung dafür vorstellen, aber es ist nur ein Beispiel.)Es sollte offensichtlich sein, dass wir setq2 nicht als Funktion definieren können. Wenn
x=50
undy=-5
, würde diese Funktion die Werte 50, -5 und 11 erhalten; es hätte keine Kenntnis davon, welche Variablen gesetzt werden sollten. Was wir wirklich sagen wollen, ist: Wenn Sie (das Lisp-System) sehen(setq2 v1 v2 e)
, behandeln Sie es als gleichwertig mit(progn (setq v1 e) (setq v2 e))
. Eigentlich ist das nicht ganz richtig, aber es wird vorerst reichen. Ein Makro ermöglicht es uns, genau dies zu tun, indem wir ein Programm zum Transformieren des Eingabemusters(setq2 v1 v2 e)
"in das Ausgabemuster" angeben(progn ...)
.Wenn Sie das nett fanden, können Sie hier weiterlesen : http://cl-cookbook.sourceforge.net/macros.html
quelle
setq2
als Funktion zu definieren, obx
undy
als Referenz übergeben werden. Ich weiß jedoch nicht, ob es in CL möglich ist. Also für jemanden, der Lisps oder CL nicht besonders kennt, ist dies kein sehr anschauliches Beispiel IMOIn Python haben Sie Dekoratoren, Sie haben im Grunde eine Funktion, die eine andere Funktion als Eingabe übernimmt. Sie können tun, was immer Sie wollen: Rufen Sie die Funktion auf, tun Sie etwas anderes, verpacken Sie den Funktionsaufruf in eine Ressourcenerfassungsversion usw., aber Sie können nicht in diese Funktion hineinschauen. Angenommen, wir wollten es leistungsfähiger machen, sagen, Ihr Dekorateur hat den Code der Funktion als Liste erhalten, dann können Sie die Funktion nicht nur so ausführen, wie sie ist, sondern Sie können jetzt Teile davon ausführen, Zeilen der Funktion neu anordnen usw.
quelle