Suche

Clojure unter der Lupe

Vektoren

Ein Clojure-Vektor erlaubt über einen ganzzahligen Index Zugriff auf seine Elemente. Dabei ist die Zugriffszeit (fast) unabhängig von der Vektorlänge. Die konkrete Syntax für die Erzeugung eines Vektors ist [<element>*]:

>> (def a-vect ["a" "b" "c" "d" ["x" "y"]]) 

Die Syntax für den Zugriff auf ein Vektorelement hat die Form eines Funktionsaufrufs. Tatsächlich kann ein Clojure-Vektor als eine Funktion aufgefasst werden, die eine natürliche Zahl auf einen Wert abbildet:

>> (a-vect 0) ;; => "a"
>> (a-vect 4) ;; => ["x" "y"]

Damit ist ein Clojure-Vektor V eine spezielle assoziative Datenstruktur, deren erlaubte Schlüsselwerte auf die natürlichen Zahlen aus [0,…,(count V)-1] eingeschränkt sind. Wie bei den Maps können auch bei einem Vektor durch (assoc <vector> <idx_i> <wert_i> …) Elemente gesetzt werden. Allerdings sind die Werte von <idx_i> auf [0,…(count <vector>))] beschränkt. Ist der Wert des Index gleich der Vektorlänge, wird der Wert am Ende des resultierenden Vektors angefügt: >> (assoc [0 1] 0 :a 1 :b 2 :c) ;; => [:a :b :c].

Umgebungen

Wenn innerhalb von Funktionen berechnete Ausdrücke häufiger verwendet werden sollen, ist es nützlich, auf den Wert über einen symbolischen Namen zugreifen zu können. Bisher haben Sie zwei Möglichkeiten kennengelernt, Symbole mit Werten zu verknüpfen: def, das ein Symbol global an einen Wert bindet. Die zweite Möglichkeit ist nicht so offensichtlich, es sind Funktionen. Innerhalb einer Funktion sind die Parameter an die Werte des Aufrufs gebunden. def hat den Nachteil, dass eine globale Bindung erzeugt wird, die Nutzung von Funktionen ist für den Zweck etwas umständlich. Daher existiert mit let eine weitere Form, mit der Sie Symbole und Werte lokal verknüpfen können: (let [<sym1> <expr1> … <symn> <exprn>] <body>*).

Die Ausdrücke <expr_i> werden nacheinander ausgewertet und an die Symbole <sym_i> gebunden. In den Ausdrücken <expr_j> können die vorhergehenden Symbole mit ihrem Wert aus dem „let“ verwendet werden. Für die <body>-Ausdrücke innerhalb des „let“s sind die Symbole an die Werte gebunden. Rückgabewert des „let“-Ausdrucks ist der letzte ausgewertete <body>. Beim Verlassen des „let“ gelten die alten Bindungen für die Symbole:

>> (def x "ix")
>> (def y "ypsilon")
>> (let [x 2 
         y (* x x)]  ;; Hier gilt x=2 
     (+ x y)) ;; => 6
>> x ;; => "ix"
>> y ;; => "ypsilon"

Auf den ersten Blick hat die „let“-Form Ähnlichkeiten mit der Verwendung von lokalen Variablen in Java oder anderen imperativen Sprachen. Es besteht jedoch der wesentliche Unterschied, dass die innerhalb des „let“ erzeugten Bindungen unveränderlich sind.

Kontrollstrukturen

Kontrollstrukturen ermöglichen es, Ausdrücke nur unter bestimmten Bedingungen auszuführen. Die einfachste Form ist (if <bedingung> <wenn-wahr> <wenn-falsch>?). Die Bedingung wird ausgewertet und wenn ihr Wert einem booleschen true entspricht, der folgende Ausdruck ausgewertet und als Wert des „if“s zurückgegeben. Als boolesches true zählen neben true alle Clojure-Werte mit Ausnahme von false und nil. Ist der Wert der Bedingung false oder nil, wird <wenn-falsch> ausgewertet und zurückgegeben. Fehlt <wenn-falsch>, so ist der Wert des „if“-Ausdrucks nil:

(if (> 2 3) :groesser :nichtgroesser) ;; =>  :nichtgroesser

Eine weitere, häufig benutzte Bedingung ist cond. (cond <test1> <expr1> … ). cond-Werte die <test_i> der Reihe nach bis zum ersten Ausdruck <expr_j> aus der „true“ liefert. Der Rückgabewert des cond-Ausdrucks ist <expr_j>. Ist keine der Bedingungen erfüllt, liefert der cond-Ausdruck nil. Soll cond in jedem Fall einen Wert ungleich nil liefern, kann als letztes Test-/Wertpaar ein Keyword und der Default-Wert verwendet werden. Zur Demonstration das Spiel „Papier/Schere/Stein“. Die „,“ in den geschachtelten c cond dienen nur der besseren Strukturierung. Kommata werden in Clojure ignoriert:

(defn papier-schere-stein [a b] 
  ;; Eine Partei wird hier leicht benachteiligt. 
  (if (= a b) :unentschieden
    (cond (= :papier a) (cond (= :schere b) b, (= :stein b) a, :else a)
          (= :schere a) (cond (= :stein b) b, (= :papier b) a, :else a)
          (= :stein a)  (cond (= :papier b) b, (= :schere b) a, :else a)
          :else b)))

if und cond sind Beispiele für „Sonderformen“. Das sind Sprachelemente, deren Argumente nicht in jedem Fall vor der Ausführung der Definition ausgewertet werden. Das unterscheidet Sonderformen von Funktionen.

Kommentare

Schreibe einen Kommentar

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