Ein sehr großer Kritikpunkt, den man immer wieder von Nicht-Lispern über Lisp hört, ist der Mangel an einer Infix-Notation. Infix-Notationen und sonstiger Syntaktischer Zucker machen einen Parser zwar komplizierter, aber können scheinbar den Code lesbarer machen(wenn man bescheuert ist).
Das sieht man besonders gut an gängigem C-Code, der ja bekanntlich vor Übersichtlichkeit nur so strotzt.
Common Lisp sei jedenfalls eine schlechte Sprache – denn die S-Ausdrücke, die man von Lisp kennt, sind kontraintuitiv. Außerdem hat Lisp keinen syntaktischen Zucker (zumindest habe ich diese Meinung von jemandem gehört, der scheinbar irgendwann dazu gezwungen wurde, Scheme zu lernen – ich rede hier von Common Lisp, nicht von Lisp oder Scheme, und ja, da gibt es einen Unterschied, Lisp ist der Sprachenstamm, und Scheme hat einen Standard, der wohl ungefähr ein zwanzigstel der Länge des Common-Lisp-Standards haben dürfte).
Nun, Common Lisp hat einen Mechanismus, um sich syntaktischen Zucker quasi selbst zu definieren. Diese Technologie wird selten genutzt, ist aber sehr praktisch. Mit ebendieser Technologie (ich benutze bewusst das Wort “Technologie”, da man jeden Web-Schrott inzwischen als “Technologie” bezeichnet) kann man quasi die Syntax von Common Lisp erweitern. Die Technologie nennt sich Reader Macro, also Lesemacro. Wer genaueres wissen will, der lese sich die Dokumentation durch.
Definiert habe ich ein Lesemacro, das an den Character ‘<’ gebunden ist – ich hätte es auch an ‘(‘ binden können, Klammern sind die Standardnotation für Instruktionen unter CL, aber das wollte ich – aus Debugtechnischen Gründen, und weil ich generell gerne auch Instruktionen eingeben können will, die nicht durch mein Macro gehen – nicht, aber ob man nun <anweisung> oder (anweisung) schreibt, ist wohl egal.
Ähnlich wie in SML kann man eine Reihe von Infix-Symbolen definieren, allerdings nicht durch einen speziellen Befehl, sondern, indem man die Symbole in eine Liste *infix-symbols* pusht. Die Präferenz wird – auch anders als in SML – nicht durch eine Zahl bestimmt, sondern dadurch, an welcher Stelle in *infix-symbols* das Symbol steht. Rechtsassoziativität und Linksassoziativität muss man nicht unterscheiden, denn ein Term der Form a + b + c wird nicht, wie man vielleicht erwarten würde, in (+ a (+ b c)) oder (+ (+ a b) c) umgewandelt, sondern in (+ a b c), d.h., das Symbol muss zu einer Funktion passen, die sich selbst um die Assoziativität kümmert – das ist rein Effizienztechnisch sinnvoll, oft kann man so einiges im Vorhinein optimieren.
Nundenn, fangen wir an: Ich befinde mich in einer REPL (“Shell”) von SBCL. Zur Erklärung: Hinter “CL-USER>” steht der Befehl, den ich gerade eingebe, darunter die Ausgabe, z.B. das Ergebnis. Zuerst setze ich mal *infix-table* auf einen einigermaßen sinnvollen Wert (einige Symbole brauch ich erst am Ende dessen, was ich zeigen will):
CL-USER> (setf *infix-symbols* '(DEFUN IF FACT PROGN FORMAT = + - * EXPT)) (DEFUN IF FACT PROGN FORMAT = + - * EXPT)
Sehr schön soweit. Jetzt testen wir mal mein Macro:
CL-USER> < 2 + 2 > 4 CL-USER> < 2 * 5 > 10 CL-USER> < 2 * 3 expt 4 > 162
Wundervoll. Einfache Sachen kriegt er also hin. Auch folgendes geht:
CL-USER> < 2 = 2 > T CL-USER> < 2 = 71 > NIL
T steht in dem Fall für “True”, NIL für “False”. Nun, mein Macro bevorzugt Infixe – heißt, wenn der zweite Ausdruck ein Infix-Symbol ist, dann wird der gesamte Ausdruck als Infix ausgewertet. Wenn nicht, wird der erste Ausdruck wie bei normalen S-Ausdrücken, als Instruktionsname genutzt. Man kann also auch sowas schreiben wie
CL-USER> < expt 2 * 5 2 * 5 > 10000000000
Das wird umgewandelt in (expt (* 5 2) (* 5 2)), da 2 kein Infix-Symbol ist. Diese Syntax ist in jedem Fall uneindeutig – ich bevorzuge im Zweifelsfall eben Infixe, man könnte auch Präfixe im Zweifelsfall bevorzugen, eine Frage des Geschmacks.
Ein noch ersprießlicheres Script kriegt man mit der Format-Anweisung. Format ist ein CL-Adäquat für die C-Funktion printf. Man übergibt ihr zwei Argumente, das erste gibt an, wohin geschrieben wird, das zweite (und die restlichen Argumente, die sind jetzt aber erstmal unwichtig), was geschrieben wird. T als zweites Argument steht für den Standard-Output.
CL-USER> (format t "Hallo Welt") Hallo Welt NIL CL-USER> < t format "Hallo Welt infixed" > Hallo Welt infixed NIL
Na, da staunt man… stderr sprintf “Hallo Welt infixed”; kann man in C nicht schreiben… Genausowenig kann man überhaupt neue Infixe definieren – dass man wenigstens die vorhandenen Infixe überladen kann, ist eine Neuerung gewesen in C++. Dabei sind doch Infixe so intuitiv. Doch es geht noch weiter. Die Funktion “defun” wird unter CL dazu benutzt, funktionen zu definieren. Wir können eine rekursive Fakultätsfunktion wie folgt definieren:
CL-USER> (defun fact (n) (if (= n 0) 1 (* n (fact (- n 1))))) FACT CL-USER> (fact 5) 120
Iiiiiegitt. Wie hässlich. Präfixschreibweise. Saubere S-Ausdrücke. Nein, sowas kann der IT-Experte nicht ertragen. In Infix-Schreibweise ist das doch viel schöner:
CL-USER> <fact defun (n . nil) defun n = 0 if 1 if n * <fact n - 1 > > FACT CL-USER> < fact 5 > 120
Mein Beitrag zur Übersichtlichkeit. Wer den Code haben will -> melden.
Den sich wundernden C++-Kundigen Informatiker, weise ich nochmals darauf hin: Diese gesamte Syntax-Struktur wurde von mir selbst definiert. Dazu musste ich nicht den Compiler patchen, sondern es reichte, die Reader-Macro-Facilities zu benutzen (heißt, ein paar Zeilen mehr eintippen, als ich hier Gepostet habe). Man kann damit auch andere lustige Sachen machen – wie gesagt, man kann, wenn man sich genug Mühe gibt, nahezu beliebige neue Syntaxstrukturen definieren. Das ist nur selten wirklich sinnvoll, aber es gibt Situationen. Außerdem macht es die Sprache erweiterbar, und damit um einiges “Mächtiger”, als C und C++, und das ohne relevante Kosten im Sinne von Laufzeit – im Gegenteil, die Compilierzeit kann sich in Extremfällen ein wenig verlängern (was daran liegt, dass ich das Macro nicht sehr efficient geschrieben habe, war mir egal), zur Laufzeit kann man damit aber sogar Vorteile herausholen, eventuell (wobei sich dazu wohl eher die Technologie der Compiler Macros benutzen lässt, auch etwas, was C nur in Spuren hat). Man gibt hier außerdem nicht Code in Form von Strings zurück, sondern in Form von “Forms”, vereinfacht ausgedrückt sind das Listen, die den Code repräsentieren.
Meiner Meinung nach ist diese Technologie zumindest in etwas abgeschwächter, hässlicherer und unsicherer, bzw. hässliche nichtssagende Compilierfehler produzierenden Form, theoretisch auch in C zugänglich, wenn man den Compiler entsprechend erweitern würde – Lesemacros brauchen ja nicht unbedingt starke Optimierung bei der Compilierung. Und meiner Meinung nach würde sie C oder wenigstens C++ auch garnicht unbedingt schaden. Vor Allem aber bei sämtlichen Scriptsprachen, bei denen so ein Mechanismus eigentlich relativ trivial implementierbar sein müsste, zum Beispiel das relativ aufgeschlossene Perl frage ich mich, warum sie nicht soetwas implementieren.
Verfasst von dasuxullebt 