Wenn man davon absieht, daß Clojure für die JVM entwickelt wurde, sehen es und Common Lisp einander sehr ähnlich. Das Makrosystem ist dasselbe, beide unterstützen interaktive Programmierung mit SLIME, und wenn man sich Module (in diesem Fall Webanwendungsframeworks) wie Hunchentoot auf der einen Seite und Compojure auf der anderen ansieht, kommt man nicht umhin, die Parallelen zwischen den beiden Sprachen anzuerkennen. Schließlich sind die Unterschiede zwischen einer Hallo-Welt-Webanwendung in der einen Umgebung und in einer in der anderen höchstens oberflächlicher Natur, und die grobe Programmstruktur ist in den beiden Fällen nicht wesentlich unterschiedlich.
Ein bißchen anders sieht es aus, wenn man in die Details geht. Hier äußert sich nämlich der große stilistische Unterschied, den man im Groben nicht sehen kann: Während auf der einen Seite in Clojure ein funktionalen Stil sowohl im Großen als auch im Kleinen üblich ist, hat man auf der anderen Seite mit Common Lisp eine Sprache, die zwar funktionale Schnittstellen, gleichwohl aber imperative Implementierungen bevorzugt.
Um ein bißchen klarer zu machen, wie sich diese subtile Unterschiedung konkret äußert, kann man als Beispiel das Generieren von HTML-Code betrachten, welches in Webanwendungen häufig vorkommt.
Nehmen wir den typischen imperativen Common-Lisp-Code, der eine kleine HTML-Seite mit einer Tabelle von Studenten mitsamt Hausaufgabenpunkten erzeugt. Hier seien students
eine Liste von Studenten, die jeweils einen Namen (name
) und eine Sequenz von Punkteeinträgen (score
) besitzen. Wir verwenden im folgenden die Yaclml-Bibliothek, welche im Stil der Verwendung sehr Common-Lisp-typisch ist.
(defun list-students (students) (with-yaclml-output-to-string (<:html (<:head (<:title "Punkteliste")) (<:body (<:h1 "Punkteliste") (<:table (<:tr (<:th "Name") (<:th "Punkte")) (loop for student in students do (<:tr (<:td (<:as-html (student-name student))) (<:td (<:as-html (format nil "~A" (student-score student)))))))))))
Wie wir sehen, handelt es sich um ein imperatives Programm. Die HTML-Tags werden durch Makros erzeugt, die, da sie Lisp-Code sind, beliebig mit anderem Lisp-Code gemischt werden können. So macht es keinen Unterschied, ob wir die <:TR
-Anweisung oder die LOOP
-Schleife innerhalb des <:TABLE
-Ausdrucks betrachten -- die Verarbeitung erfolgt stets gleich, nämlich als Programmcode. Genauso können wir ein <:TR
als Anweisung innerhalb der Schleife ausführen.
In anderen Worten: Yaclml stellt uns Anweisungen zur Verfügung, die HTML-Tags ausgeben. Das ist ein zutiefst imperatives Konzept. Unsere list-students
-Funktion allerdings hat eine funktionale Schnittstelle: Wenn man sie aufruft, erhält man als Ergebnis einen String mit dem HTML-Code (dafür sorgt das Makro with-yaclml-output-to-string
, das den entsprechenden Kontext für die Codeausführung auf- und am Ende wieder abbaut); Nebenwirkungen hat die Funktion keine.
Man beachte, daß im imperativen Code oben zu keinem Zeitpunkt eine Baumstruktur des zu generierenden HTML-Codes existiert. Der Code wird ausgeführt und gibt nebenbei Zeichenketten aus, aber das geschieht linear. Die HTML-Baumstruktur ist nur durch die Schachtelung des Codes gegeben und wird nie explizit gemacht.
Betrachten wir nun den entsprechenden Code in Clojure unter Verwendung der HTML-Generierungsfunktionalität des Compojure-Frameworks.
(defn list-students [students] (html [:html [:head [:title "Punkteliste"] [:body [:h1 "Punkteliste"] [:table [:tr [:th "Name"] [:th "Punkte"]] (map (fn [student] [:tr [:td (:name student))) [:td (str (:score student))]]) students)]]]))
Was passiert in diesem Codeschnipsel? Erst einmal wird ein Baum in Form einer verschachtelten Liste aufgebaut. Der map
-Aufruf erzeugt eine Sequenz von Listen, die in die :table
-Liste eingefügt werden. Am Ende wird alles einer Funktion mit dem Namen html
gegeben, welche aus der Baumstruktur den fertigen HTML-Code konstruiert.
Auch der Clojure-Code mischt Code mit HTML-Struktur, aber auf eine andere Weise. Anstatt einen großen Haufen Code zu schreiben, der sich zufälligerweise zum Teil wie die Daten liest, die am Ende herauskommen sollen, macht Clojure die Trennung von Code und Daten expliziter. Code sind hier nur der map
- und der str
-Aufruf sowie der Aufruf der html
-Funktion ganz außen. Alles andere besteht aus Listen, die nicht als Programme ausgewertet werden.
Es ist zu bemerken, daß die Syntax von Clojure hier eine wesentliche Rolle spielt. Gäbe es die Vektor-Syntax mit den eckigen Klammern nicht, so wäre der obige Code wesentlich umständlicher zu schreiben, da man Auswertung und Nichtauswertung der Baumstruktur explizit abwechseln müßte.
Genau diesen Ansatz verfolgt eine andere in der Common-Lisp-Welt beliebte HTML-Generierungsbibliothek namens CL-WHO. Der obige Code würde basierend auf ihr wie folgt aussehen:
(defun list-students (students) (with-html-output-to-string (:html (:head (:title "Punkteliste") (:body (:h1 "Punkteliste") (:table (:tr (:th "Name") (:th "Punkte")) (loop for student in students do (htm (:tr (:td (esc (student-name student)))) (:td (esc (format nil "~A" (student-score student)))))))))))
Hier wird also die Listenstruktur wieder explizit gemacht, aber nicht so, daß lokal syntaktisch klar ist, wo und wo nicht. Die einzige Möglichkeit, Code von Nichtcode zu unterscheiden, ist hier, in die jeweiligen Strukturen hineinzusehen und das Symbol zu betrachten, das die Liste einleitet (ist es ein Schlüsselwort, d.h. ein Symbol mit Doppelpunkt vorne dran, handelt es sich um Daten; außerdem handelt es sich um Daten, wenn das ganze Ding ein String ist; ansonsten handelt es sich um Code); zusätzlich muß man ein paar Ebenen nach oben gehen, um zu sehen, ob man überhaupt in einem CL-WHO-Kontext ist (d.h. innerhalb von htm
oder with-html-output
) -- denn sonst muß man alles als Code interpretieren und nichts als Daten.
Mag sein, daß ich hier mit meiner Meinung allein auf weiter Flur stehe, aber man erlaube mir die Bemerkung, daß mir diese undurchsichtige Vermischung von Code und Daten nicht gerade erstrebenswert anmutet.
Unter demselben Problem leiden aber weder die rein imperative noch die funktionale Variante. Beiden ist eine gewisse elegante Einfachheit und Eindeutigkeit gemein -- obschon sie prinzipiell entgegengesetzte Ansätze verfolgen. Konsequentheit wiegt eben schwerer als Philosophie.
Comments
Submit a comment
Note: This website uses a JavaScript-based spam prevention system. Please enable JavaScript in your browser to post comments. Comment format is plain text. Use blank lines to separate paragraphs.