Zusammengesetzte Daten in Clojure
Dieses Posting setzt unsere Clojure-Einführung (hier Teil 1, Teil 2, Teil 3) fort. Dieses Mal geht es um zusammengesetzte Daten.
In Teil 2 der Clojure-Einführung haben wir uns mit den eingebauten Datenstrukturen beschäftigt. Heute behandeln wir die Definition neuer zusammengesetzter Datentypen.
Als Beispiel gehen wir in einen Computerladen und stellen uns einen Individualrecher aus Einzelteilen zusammen. Um diese Zusammenstellung zu beschreiben (zum Beispiel für einen Online-Shop), entwerfen wir eine Repräsentation als Daten. Dieser Prozess fängt mit einer Datendefinition an:
Die Datendefinition sagt klar aus, dass ein Computer aus mehreren
Teilen besteht, wir brauchen also zusammengesetzte Daten. In Clojure
sind für zusammengesetzte Daten Records zuständig, und wir können
einen Record-Typ für zusammengesetzte Daten mit der
defrecord
-Form
definieren:
Diese Definition stellt eine Java-Klasse names Computer
her, mit
Feldern processor
, ram
und hard-drive
. (Java-Programmierer
würden Computer
eine POJO-Klasse nennen. Da Java-Klassen
in der Regel mit einem Großbuchstaben anfangen, übernehmen wir die
Konvention für Record-Typen.) Damit können wir das in Clojure
eingebaute
new
-Konstrukt
verwenden, um Computer
-Objekte herzustellen:
Wir können new Computer
abkürzen mit Computer.
(beachten Sie den Punkt):
Allerdings ist zu berücksichtigen, dass Computer.
keine Funktion, sondern
ein Makro ist. Wenn wir versuchen, sie als eigenständiges Objekt zu
verwenden, passiert folgendes:
Clojure stellt aber glücklicherweise auch noch eine
Konstruktor-Funktion namens ->Computer
zur Verfügung:
Die einzelnen Teile eines Records können wir folgendermaßen extrahieren:
Die Keywords mit den Feldnamen fungieren also als Selektoren - das liegt daran, dass in Clojure jeder Record auch als Map funktioniert. (Siehe unsere Einführung in Datentypen.)
Das reicht eigentlich schon, um mit Records in Clojure zu hantieren. Allerdings tauchen zusammengesetzte Daten oft auch als Fälle in gemischten Daten auf, wofür wir noch ein Prädikat für einen Record-Typ brauchen, also eine Möglichkeit, Objekte der Record-Definition von anderen Objekten zu unterscheiden.
Wir schreiben zur Illustration ein Programm zur Fütterung zweier Sorten Tiere: Gürteltiere und Papageien.
Hier ist eine Datendefinition für Gürteltiere, eine dazu passende Record-Definition und ein Beispiel-Gürteltier:
… und hier ist das gleiche nochmal für den Papagei:
Ein Gürteltier zu füttern erhöht dessen Gewicht, zumindest wenn es noch lebt:
Bei einem Papagei klappt das immer:
Nun schreiben wir eine Funktion, die sich sowohl für Gürteltiere als auch für Papageien zuständig fühlt. Die Datendefinition dafür sieht so aus:
Um eine Funktion feed-animal
zu schreiben müssen wir eine
Verzweigung je
nachdem machen, ob es sich bei einem Tier um ein Gürteltier oder einen
Papagei handelt. Das funktioniert mit
instance?
.
(instance? C x)
testet, ob x
eine Instanz der Klasse C
ist,
insbesondere also, ob x
zum Record-Typ C
gehört:
Damit wären alle Bestandteile beisammen, die wir für den Umgang mit neuen Typen für zusammengesetzte Daten brauchen: Konstruktor, Selektoren und Prädikat. Genug für heute!