Ich lerne Common Lisp von Practical Common Lisp . Es gibt ein Beispiel für Hilfsfunktionen zum Lesen und Schreiben von Binärdateien in Kapitel 24. Hier ein Beispiel:
%Vor%Ich kann auch Funktionen schreiben, um andere Arten von Binärzahlen zu lesen. Aber ich dachte, dass dies das DRY-Prinzip verletzt. Außerdem werden diese Funktionen ähnlich sein, also habe ich versucht, die Funktionen mit Makros zu erzeugen.
%Vor%Es funktioniert. Es erzeugt Funktionen zum Lesen und Schreiben von vorzeichenlosen und vorzeichenbehafteten Ganzzahlen in den Größen 1, 2, 4 und 8. SLIME versteht es. Aber ich frage mich, ob es bessere Möglichkeiten gibt.
Was ist der beste Weg, um eine Reihe ähnlicher Funktionen in Common Lisp zu schreiben?
Es gibt einige Probleme mit diesem Code, obwohl der generelle Ansatz, Makros zu erzeugen, in Ordnung ist.
Benennung
Die Makros sollten nicht make-...
heißen, weil sie keine Funktionen sind, die etwas ausmachen, sondern Makros, die eine Funktion definieren.
Codegenerierung
Der EVAL-WHEN ... EVAL
Code ist wirklich schlecht und sollte nicht auf diese Weise verwendet werden.
Der bessere Weg ist das Schreiben eines Makros, das mit den Funktionsdefinitionen in ein progn
expandiert.
Wenn ich EVAL
verwenden möchte, müsste ich keine Code-generierenden Makros schreiben, sondern nur Code-Generierungsfunktionen. Aber ich möchte nicht EVAL
verwenden, ich möchte direkt Code für den Compiler erstellen. Wenn ich code-generierende Makros habe, brauche ich nicht EVAL
.
EVAL
ist keine gute Idee, weil es nicht klar ist, dass der Code kompiliert werden würde - was von der Implementierung abhängig wäre. Auch die Auswertung würde zur Kompilier- und Ladezeit stattfinden. Es wäre besser, die Funktionen zur Kompilierzeit zu kompilieren und sie nur zur Ladezeit zu laden. Ein Dateicompiler könnte auch mögliche Optimierungen für die ausgewerteten Funktionen übersehen.
Anstelle von EVAL-WHEN ... EVAL
definieren wir ein anderes Makro und verwenden es später:
Jetzt können wir das obige Makro verwenden, um alle Funktionen zu generieren:
%Vor%Sie können die Erweiterung hier sehen:
%Vor%Jedes Unterformular wird dann zu den Funktionsdefinitionen erweitert.
Auf diese Weise führt der Compiler die Makros aus, um den gesamten Code zur Kompilierzeit zu generieren, und der Compiler kann dann Code für alle Funktionen generieren.
Effizienz / Standardwerte
In einer Funktion der niedrigsten Ebene möchte ich möglicherweise keinen Parameter &optional
verwenden. Der Standardaufruf würde den Wert einer dynamischen Bindung erhalten, und schlimmer noch, *standard-input*
/ *standard-output*
ist möglicherweise kein Stream, für den READ-BYTE
oder WRITE-BYTE
funktioniert. Nicht in jeder Implementierung können Sie einen Standard-Input / Output-Stream als binären Stream verwenden.
LispWorks:
%Vor%Ich möchte auch erklären, dass alle generierten Funktionen inline sind.
Typdeklarationen wären eine andere Sache, über die man nachdenken sollte.
Zusammenfassung : Verwenden Sie nicht EVAL.
Im Allgemeinen würde ich es vorziehen, einfach die Anzahl der zu lesenden Bytes als einen anderen Parameter zu der Funktion hinzuzufügen:
%Vor%Signedness und Endianness können als Schlüsselwortargumente hinzugefügt werden. Diese Art der Programmierung ist gut für verständlichen Code, der auch durch Tools wie SLIME leicht navigiert werden kann.
Das Abrollen durch Makros ist eine gültige Optimierungsstrategie, und ich verzichte darauf Rainers Antwort .
Im konkreten Fall des Lesens von Zahlen aus einem Datenstrom ist die Optimierung wahrscheinlich von Anfang an ein gültiges Ziel, da dies in engen Schleifen häufig verwendet wird.
Wenn Sie dies tun, sollten Sie jedoch gründlich dokumentieren, was generiert wird. Wenn ein Leser des Codes einen Operator read8bes
sieht, kann er nicht leicht herausfinden, wo er definiert wurde. Du musst ihm helfen.
Tags und Links dry common-lisp preprocessor