Spracheigenschaften

Gesamtübersicht: Spracheigenschaften


Dynamisches Entwickeln (REPL)

Clojure bietet eine interaktive Konsole, die Read-Eval-Print-Loop (REPL). Hier kann Code eingegeben werden, welcher dann vom Reader in einen Programmbaum überführt wird und ausgeführt werden kann. Das Resultat jeder Auswertung wird direkt angezeigt.

Intern arbeitet die REPL genau wie bei der Verarbeitung einer Quelldatei, weshalb auch keine Unterschiede entstehen. Die REPL ist also ein vollwertiges Programmierwerkzeug.

Weiterhin kann die REPL auch während des Programmlaufs auf einem Socket lauschen, wodurch z.B. das laufende Programm überwacht oder auch ergänzt werden kann.


Syntax

Die Syntax leht sich stark an die von Lisp an. Es gibt nur wenige Unterschiede:

Ein Clojure Programm ist also eine Liste von Symbolen, wobei immer das erste Symbol einer Liste als Operand verwendet wird, alles was folgt sind die Argumente.


Typisierung

Clojure ist, wie Lisp, dynamisch getypt. Es werden grundsätzlich Symbole auf Speicherzellen (Vars) gebunden. Dadurch wird der Inhalt einer Speicherzelle über einen symbolischen Namen zugreifbar. Es werden keine Typinformationen angegeben. Es ist jedoch möglich zur Beschleunigung des Codes sogenannte Type-Hints für Parameter von Funktionen und deren Rückgabewerte anzugeben. Wird auf einem mit Type-Hint versehen Parameter eine Operation ausgeführt, die nicht immer möglich ist, so wird der Wert vorher auf den angegebenen Typen gecastet. Andernfalls würde mittels Java-Reflection überprüft werden, ob die Operation möglich ist.

Type-Hints sind Teil der Metadaten.

Um bestehende Java-Bibliotheken nutzen zu können, müssen jedoch deren Argumentlisten berücksichtigt werden. Zu diesem Zweck gibt es Funktionen, welche Objekte, Arrays und primitive Typen erzeugen. Weiterhin können auch Java-Klassen mit Clojure erzeugt werden.


Funktionen

Funktionen werden mittels fn erzeugt. Die Argumente werden in einem Vektor übergeben. Daran anschließend folgt der Funktionsrumpf. Das letzte Resultat im Rumpf wird zum Funktionsergebnis.

Es ist auch möglich, dass Funktionen mehrere unterschiedlich lange Argumentlisten haben. In dem Fall wird für jede Variante eine Liste mit den Elementen Argumentvektor und Funktionsrumpf angegeben.

Ein Name wird mittels des normalen def Operators angegeben. Es gibt aber auch eine verkürzte Form: defn.

;Anonyme ID-Funktion
(fn [x] x)

;ID-Funktion mit Namen
(def id-fct (fn [x] x))

;oder kürzer
(defn id-fct [x] x)

;mehrere Argumentlisten
(fn
  ([a] a)
  ([a b] (+ a b)))

Funktionen sind normale Werte, können also sowohl als Parameterwerte als auch als Rückgabewerte auftreten.

Jede Funktion wird in eine Java-Klasse compiliert, welche die Schnittstelle Runnable und Callable implementiert. Eine Funktion kann damit auch nebenläufig in einem eigenen Thread ausgeführt werden, das Ergebnis kann später erfragt werden. Genaueres dazu kann in der Java Dokumentation für java.util.concurrent nachgelesen werden.


Metadaten

Metadaten sind Daten über Daten. Diese werden in Clojure mit Hilfe von Tabellen realisiert. Metadaten können sowohl an Symbole als auch an Sequenzen angehängt werden. Sie verändern dabei die eigentlichen Daten nicht. Anders als Java-Annotations gibt es keinen festgelegten Satz von möglichen Auszeichnungen, sondern es können beliebige Tabelleneinträge (indiziert durch Keywords) angelegt werden. Es werden nur einige davon durch Sprachfunktionen ausgewertet. Dazu gehören:

Keyword Bedeutung
:doc Dokumentation einer Funktion
:arglists Die verschiedenen Parameterlisten
:ns Namespace
:name Lokaler Name
:tag Erwarteter Argument oder Rückgabetyp
:pre / :post Vor-, Nachbedingung (erst in Git-Version)
:test Testfälle (erst in Git-Version)

Multimethoden

Multimethoden ermöglichen einen Dispatch über alle Parameter einer Funktion. Dies geschieht zur Laufzeit durch die Auswertung einer Dispatch-Funktion. Der zurückgelieferte Wert bestimmt die auszuführende Funktion. Beim Aufruf einer Multimethode werden also immer zwei Funktionsaufrufe realisiert.

Bei der Auswertung der Dispatch-Funktion kann sowohl auf den Typ, den Wert, die Attribute und Metadaten sowie auf Beziehungen zwischen den Argumenten zugegriffen werden.

(defn dispatch-funktion [a b]
  (cond
    (= (type a) (type b)) :equal
    :otherwise :not-equal))

(defmulti multimethode dispatch-funktion)

(defmethod multimethode :equal [a b]
  (println "Types of " a " and " b " are equal."))

(defmethod multimethode :default [a b]
  (println "I don't know anything about " a " and " b "."))

Hier wird von der Dispatch-Funktion der Typ der beiden Argumente miteinander verglichen. Bei Gleichheit wird die erste Methode ausgeführt, in allen anderen Fällen die Default-Methode. Hier ist keine Methode für den Fall der Ungleichheit angegeben, damit wird auch dies in der Default-Methode behandelt. Die Anzahl an verschiedenen Methoden ist nicht beschränkt.


Makrosystem

Clojure bietet auch ein Makrosystem, mit dem Codetransformationen zur Compilezeit möglich sind. Dieses lehnt sich sehr stark an jenes von Common Lisp an, so dass Informationen z.B. aus Peter Seibels Practical Common Lisp entnommen werden können.


Refsystem

Um einen Zustand trotz unveränderlicher Datenstrukturen und mit koordinierten Zustandsübergängen anzubieten ist in Clojure ein Refsystem integriert. Es funktioniert, indem ein Symbol auf ein unveränderliches Ref-Var gebunden wird. Dieses Ref besitzt eine veränderliche Referenz auf ein beliebiges anderes Var.
Ref-Var-Beziehung

Durch derefenzieren des Refs kann auf den eigentlichen Inhalt zugegriffen werden. Dieser ist dank unveränderlicher Datenstrukturen immer konsistent und braucht daher auch nicht gelockt werden. Es gibt verschiedene Ausprägungen von Refs, die unterschiedliche Eigenschaften aufweisen:

Typ Eigenschaften
Ref synchron, transaktionsbasiert (Software Transactional Memory, STM)
Atom synchron
Agent asynchron
dynamic Var Jeder Thread kann Vars unterschiedlich binden, dies hat keinen Einfluss auf andere Threads

Das Verändern der Referenz kann nur unter bestimmten Bedingungen (z.B. innerhalb einer Transaktion) mit bestimmten Funktionen erfolgen. Damit kann sichergestellt werden, dass Referenzen immer gültig sind.

Ein ausführlicher Vortrag von Rich Hickey zu diesem Thema kann auf blip.tv abgerufen werden.