Was Sie für den Start in den Lisp-Dialekt alles wissen sollten

Clojure unter der Lupe

Burkhard Neppert

Clojure ist eine Sprache aus der Lisp-Familie. Als Mitglied dieser Familie erbt Clojure einige Eigenschaften, die es von Sprachen wie Java oder C unterscheiden. Der vordergründig größte Unterschied ist die Syntax. Lisp-Programme sehen für Programmierer mit C-/Java-Hintergrund einfach ungewohnt aus. Die Syntax ist aber logisch und kommt mit erheblich weniger Regeln als andere Sprachen (vielleicht ausgenommen Forth) aus. Ein weiterer Unterschied zu vielen „Mainstream“-Sprachen ist die Tatsache, dass Clojure eine funktionale Sprache ist. Funktionen sind das zentrale Element der Sprache, Seiteneffekte werden soweit möglich vermieden.

Der typische Entwicklungsprozess in Lisp/Clojure vollzieht sich in einer interaktiven Umgebung, der Read-Eval-Print-Loop (REPL). Die Clojure-REPL ist ein Java-Programm, das in einer Java-VM ausgeführt wird. Sie starten eine Clojure-REPL in einer Kommandozeile durch java -jar clojure.jar clojure.main. Für erste Schritte ist die REPL ausreichend, komfortableres Arbeiten wird aber mit Plug-ins für Eclipse [1], Netbeans, IntelliJ sowie auf Emacs [2] möglich.

Grundlegende Dinge

Jedes Clojure-Programm besteht aus einer Reihe von Ausdrücken. Diese werden von der Read-Eval-Print-Loop (REPL) gelesen und ausgewertet und das Ergebnis der Auswertung als Resultat ausgegeben. Während der Auswertung können Seiteneffekte wie Ein-/Ausgabeoperationen auftreten. Es sind verschiedene Typen von Ausdrücken vorhanden, die nach unterschiedlichen Regeln ausgewertet werden.

Konstanten

Zahlen wie 1, 2.71828 oder Zeichenketten wie „Foo“ sind konstante Ausdrücke in Clojure und werden zu sich selbst ausgewertet:

>> 10         ;; => 10  Kommentare in Clojure: Zeile ab ";" wird ignoriert
>> "Ideenlos" ;; => "Ideenlos"

Neben Zahlen und Zeichenketten existieren noch die booleschen Konstanten true und false sowie der besondere Wert nil, der Java null entspricht. Weiter gibt es in Clojure so genannte „Keywords“. Das sind Konstanten, die zu sich selbst ausgewertet werden. Die konkrete Syntax ist ein „:“ gefolgt von einem Bezeichner, z. B. :ein-keyword. Eine häufige Nutzung von Keywords ist als Schlüssel für assoziative Datenstrukturen und für Multimethoden.

Symbole

Symbole sind in Clojure Bezeichner, die für andere Werte stehen. Ein Symbol wird durch (def <symbol> <ausdruck>) an einen Wert gebunden. Bei der Auswertung liefert das Symbol dann den Wert, an den es gebunden wurde. Die Auswertung eines Symbols, das noch nicht gebunden wurde, ist ein Fehler:

>> ein-symbol          ;; => Unable to resolve symbol: ein-symbol in this context 
>> (def ein-symbol 42) ;; => #'user/ein-symbol
>> ein-symbol          ;; => 42

Soll das Symbol selbst als Wert verwendet werden, muss ein „‚“ vorangestellt werden (der Ausdruck wird „gequoted“):

>> frei ;; => Unable to resolve symbol: frei in this context 
>> 'frei ;; => frei
>> (def unfrei 'frei)
>> unfrei => frei

Die durch def hergestellte Symbol-/Wert-Bindung ist global, d. h. überall sichtbar, sofern nicht durch andere Bindungskonstrukte das Symbol lokal an einen anderen Wert gebunden wurde (mehr dazu bei „let“ und Funktionen).

Listen

Listen haben in Clojure eine doppelte Funktion: Zum einen dienen sie als einfache Datenstruktur, die beliebige Clojure-Ausdrücke (also auch wieder Listen, (die wiederum Listen (mit Listen …))) enthalten kann. Zum anderen werden Listen bei der Auswertung als Funktionsaufrufe behandelt. Die Syntax für eine Liste ist (<ausdruck>*).

Listen als Datenstruktur

Soll eine Liste als Datenstruktur verwendet werden, so ist ein „‚“ vor die äußerste öffnende Klammer zu setzen (im Lisp-Jargon: der Ausdruck wird „gequoted“). Dadurch wird die Liste aller nicht ausgewerteten Ausdrücke in der Liste zurückgegeben.

Ist eine Liste gewünscht, die die Ergebnisse der Auswertung der Elemente enthält, kann die Funktion list verwendet werden:

>> (def a1 "erster")
>> (def a2 "zweiter")
>> (def a3 "dritter")
>> '(a1  (a2  a2)) ;; => (a1 (a2 a3))
>> (list a1 a2 a3) ;;  => ("erster" "zweiter" "dritter")

Für den Umgang mit Listen enthält Clojure eine große Menge von Funktionen, hier nur die allerwichtigsten:

  • count: für jede Liste (und Clojure-Collection) liefert (count <liste>) die Zahl der Elemente.
  • First: Der Ausdruck (first <liste>) liefert das erste Element einer Liste und bei einer leeren Liste ‚()den besonderen Wert für „nichts“, nil.
  • rest und next: der Ausdruck (rest <liste>) liefert eine Liste ohne das erste Element und bei einer leeren Liste ‚()die leere Liste. next verhält sich ähnlich wie rest, nur wird hier in Fällen, in denen rest die leere Liste liefert, nil zurückgegeben.
  • conj: Listen werden durch (conj <liste> <element>+) konstruiert, wobei die Elemente an den Anfang der Liste gestellt werden:
>> (count '(1 2 3)) ;; => 3
>> (first '(1 2 3)) ;; => 1
>> (rest '(1 2 3)) ;; => (2 3)
>> (conj '() 1 2 3 4) ;; => (4 3 2 1)

Wichtig ist, dass durch diese Operationen auf Listen die Argumente nicht verändert werden, sondern neue Listen erzeugt werden:

>> (def Leer '())
>> (conj Leer 3 2 1) ;; => (1 2 3)
>> Leer ;; => () 
Geschrieben von
Burkhard Neppert
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.