Viele Dinge zeichnen Lisp aus (das Objektsystem, das Makrosystem, die interaktive Entwicklungsweise, das condition system etc.), aber eines, das besonders häufig genannt wird, ist die Äquivalenz von Daten und Code.
Nun sind Daten und Code in Lisp in der Tat gewissermaßen „äquivalenter“ als in anderen Sprachen. Das Wort Äquivalenz klingt aber, als würde es etwas Absolutes bezeichnen. Das ist unglücklich, denn es gibt dabei wie bei den meisten Aspekten aller Dinge in dieser Welt durchaus Abstufungen und verschiedene Ausprägungen. Um das zu durchschauen, muß man aber erst einmal verstehen, worin diese sogenannte Äquivalenz von Code und Daten eigentlich besteht.
Daten sind Code!
Zunächst kann man feststellen, daß Lispcode quasi in einem mit XML vergleichbaren (aber besser für Code geeigneten) Format geschrieben wird. S-Expressions (zu deutsch: symbolische Ausdrücke) kann man sowohl für die Darstellung von Daten als auch von Code sehr gut verwenden. Da das in Lisp auch getan wird, kann Code wie ein Datensatz behandelt werden. Makros machen sich das zunutze, um Code zur Kompilierzeit zu transformieren (Stichwort: statische Metaprogrammierung).
Doch ziehen wir es einmal von der anderen Seite auf. Angenommen, wir wollten eine Klassenhierarchie (also Daten) beschreiben. In XML könnte man die Definition einer Klasse person
wie folgt angeben:
<class name="person" superclass="creature"> <property name="name" accessor="person-name" initarg="name" initform="Mary Unnamed"/> <property name="birthday" accessor="person-birthday" initarg="birthday"/> </class>
Wir können das direkt in S-Expressions übersetzen:
(class :name "person" :superclass "creature" (property :name "name" :accessor person-name :initarg :name :initform "Mary Unnamed") (property :name "birthday" :accessor person-birthday :initarg :birthday))
Wenn wir die Konventionen ein bißchen weg von XML hin zu Lisp anpassen, ist es ebenso natürlich, stattdessen folgendes zu schreiben:
(defclass person (creature) ((name :accessor person-name :initarg :name :initform "Mary Unnamed") (birthday :accessor person-birthday :initarg :birthday)))
Hier ist jedoch etwas interessantes geschehen: Der vorangehende S-Ausdruck beschreibt nicht mehr nur Daten. Vielmehr handelt es sich um ausführbaren Code. (Man überzeuge sich selbst davon, indem man ihn in eine Common-Lisp-Implementierung eingibt.)
In anderen Worten: Die „Äquivalenz“ von Code und Daten in Lisp beschränkt sich gerade nicht auf die lediglich oberflächliche Gleichheit der verwendeten Datenstrukturen. (Tatsächlich wird der Stellenwert von Listen als Datenstrukturen in Lispprogrammen weithin überschätzt.) Vielmehr verschwimmt die Grenze zwischen Daten und Code auf eine ganz konkrete Art und Weise, nämlich indem die Daten, die wir schreiben, zugleich als Code dienen.
Einerseits könnte man die fehlende Trennung von Daten und Code als Schlampigkeit werten. Tatsächlich wird ja häufig davor gewarnt, zu viel zu „hardcoden“. Natürlich gilt das für Lispcode genauso, aber die Frage, was eigentlich unter „Hardcoden“ fällt und was nicht, ist nicht so einfach wie in einer Sprache wie C++. Schließlich können solche Dinge wie Klassendefinitionen in Lisp ganz einfach im laufenden Betrieb neu geladen werden, wonach jede Instanz der Klasse die neue Klassenstruktur automatisch übernimmt — ganz so also, als handelte es sich „nur“ um Daten, die man mal eben neu einliest.
In der Praxis: Eingebettete Daten...
Interessant wird es aber spätestens dann, wenn man sich nicht sicher ist, ob etwas, das man definieren will, eher als Daten oder als Code dargestellt werden soll. Stellen wir uns ein Computerspiel mit verschiedenen Monstertypen vor. Wie modellieren wir die Monster? Bekommt jeder Monstertyp eine Klasse auf der Programmebene, oder schreiben wir eine gemeinsame Klasse Monster
für alle Monstertypen und lesen die Monsterdaten dann aus Dateien ein? Falls wir den datengetriebenen Ansatz wählen: Was tun wir, wenn wir das Verhalten von Monstern je nach Klasse anpassen wollen? Betten wir eine Skriptsprache in die Datendefinitionen ein oder hardcoden wir die Verknüpfung zwischen Klasse und Verhalten dann doch wieder direkt? Aber inwiefern handelt es sich noch um Daten, wenn wir plötzlich eine ganze Programmiersprache eingebettet haben?
Offenbar verschwimmt hier die Grenze zwischen dem, was man als Daten und dem, was man als Teil des Programms betrachten kann. Ein Lispprogrammierer ist da fein heraus: Er bastelt sich ein Makro, sagen wir define-monster-class
, und mischt Daten frei mit Code:
(define-monster-class computer-scientist (humanoid) (stats (health 30) (strength 20) (charisma -10) (intelligence 90)) (extra-talents (coffee-tolerance 100) (keyboard-cleaning 40)) (natural-enemy-reactions (patent-lawyer (enemy) (make-jokes-about enemy) (glare-at enemy)) (end-user () (run-away))) (on battle-entry (me enemies) (cry "You will be repartitioned!") (tell-others (protect me) (attack enemies)) (make-awkward-gesture) (attack enemies)))
...gibt es nicht nur in Lisp.
So weit, so gut. Aber widmen wir uns anderen Programmiersprachen. Wie nah an Lisp können wir herankommen, um unsere Daten in Code einzubetten? In Ruby bekommt man mit entsprechender Metaprogrammierung folgendes hin:
class ComputerScientist < Humanoid stat :health, 30 stat :strength, 20 stat :charisma, -10 stat :intelligence, 90 extra_talents { :coffee_tolerance => 100, :keyboard_cleaning => 40 } on :natural_enemy_encounter, PatentLawyer do |enemy| make_jokes_about enemy glare_at enemy end on :natural_enemy_encounter, EndUser do run_away end on :battle_entry do |enemies| cry "You will be repartitioned!" tell_others { |me| protect me attack enemies } make_awkward_gesture attack enemies end end
Zugegeben: Es ist insofern ein konstruiertes Beispiel, als es sich hier um etwas handelt, das ein traditionelles Klassensystem gut modellieren kann. Aber ähnliche Techniken kommen zum Beispiel in Ruby on Rails für die Angabe von Datenbankschemata zum Einsatz:
ActiveRecord::Schema.define do create_table :articles do |table| table.column :id, :integer table.column :title, :string table.column :author, :string table.column :body_text, :string table.column :published, :datetime end end
Es ist sicherlich nicht weit hergeholt, zu behaupten, es handle sich bei einem Datenbankschema um Daten, nicht so sehr um Code. Ruby erlaubt es, diese Daten recht natürlich in Code einzubetten. Die Sprache hat eben eine große philosophische Nähe zu Lisp.
Was ist mit anderen Sprachen? In JavaScript könnte man so etwas wie das Folgende relativ leicht umsetzen:
var ComputerScientist = makeMonsterType(Humanoid, { stats: { health: 30, strength: 20, charisma: -10, intelligence: 90 }, extraTalents: { CoffeeTolerance: 100, KeyboardCleaning: 40 }, naturalEnemies: { PatentLawyer: function(enemy) { this.makeJokesAbout(enemy); this.glareAt(enemy); }, EndUser: function() { this.runAway(); } }, onBattleEntry: function(enemies) { this.cry("You will be repartitioned!"); this.tellOthers(function(me) { this.protect(me); this.attack(enemies); }); this.makeAwkwardGesture(); this.attack(enemies); } });
Das ist schon nicht mehr ganz so lispig wie die Ruby-Variante, weil Code und Daten strikter getrennt sind. Außerdem hat man das Problem des Neuladens: Wie sorgt man nach dem Verändern des Codes dafür, daß bestehende ComputerScientist
s die neuen Attribute bekommen? (Ruby verhält sich diesbezüglich wie Lisp.) Immerhin aber gibt es die passende Notation für Daten sowie starke dynamische Metaprogrammierungsfähigkeiten in der Sprache — damit ist schon einiges machbar.
In Python dagegen sieht es schon wesentlich düsterer aus. Man müßte etwas wie das Folgende schreiben, um noch einigermaßen innerhalb der Konventionen der Sprache zu bleiben (und auch das geht nur deshalb noch relativ gut, weil wir etwas modellieren, das sich gut in Klassen ausdrücken läßt):
class ComputerScientist(Humanoid): health = 30 strength = 20 charisma = -10 intelligence = 90 talents = dict(Humanoid.talents, CoffeeTolerance=100, KeyboardCleaning=40) def on_battle_entry(self, enemies): self.cry("You will be repartitioned!") self.tell_others(Creature.protect, self) self.tell_others(Creature.attack, enemies) self.make_awkward_gesture() self.attack(enemies) @natural_enemy_reaction(PatentLawyer) def patent_lawyer_reaction(self, enemy): self.make_jokes_about(enemy) self.glare_at(enemy) @natural_enemy_reaction(EndUser) def end_user_reaction(self, enemy): self.run_away()
Zugegeben: Es könnte schlimmer sein. Dennoch weicht der Code schon stärker von dem ab, was man intuitiv schreiben würde, um die Daten zu beschreiben — ganz zu schweigen davon, daß man mit Python (im Gegensatz zu Ruby!) dieselben Probleme wie mit JavaScript hat, was das Neuladen der Klassendefinition angeht.
In Sprachen wie Java oder C++, deren Metaprogrammierungsfähigkeiten wesentlich geringer sind, geht das Ganze natürlich erst recht nur mit argen Verrenkungen (wie z.B. externen Codegeneratoren und fragwürdigen Linker-Tricks), so daß stattdessen dann meist eine Skriptsprache eingebettet und jede Form von Vermischung von Daten und Code als Hardcoding geächtet wird.
Conclusio
Das Verschwimmenlassen von Code mit Daten ist keine simple Ja/Nein-Frage. Natürlich macht auch die oberflächlichere strukturelle Äquivalenz von Daten und Code in Lisp es einem leichter, die entsprechende de-facto-Äquivalenz herzustellen und auszunutzen. Und doch gibt es sie in verschiedenen Ausprägungen in unterschiedlichen Programmiersprachen und Kontexten. Letztlich ist sie nichts weiter als eine Form von Deklarativität, in der der Programmierer die deklarativen Elemente zu einem gewissen Grad selbst definiert.
Man darf bei alledem aber nicht vergessen, daß die Praktikabilität eines Paradigmas in einer gegebenen Programmiersprache auch stets eine kulturelle Frage ist. Wie realistisch es ist, Metaprogrammierung und in die Sprache eingebettete anwendungsspezifische Subsprachen zu verwenden, hängt schließlich nicht zuletzt damit zusammen, wie gut es von den verfügbaren Entwicklungswerkzeugen und Bibliotheken unterstützt wird.
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.