; Kommentare starten mit einem Semikolon. ; Clojure wird in "Forms" geschrieben, was nur Listen von Dingen ; in Klammern sind, getrennt durch Leerzeichen. ; ; Der Clojure Leser nimmt an, dass das Erste, was aufgerufen wird ; eine Funktion oder ein Makro ist, der Rest sind Argumente. ; Der erste Aufruf in einer Datei sollte ns sein, um den Namespace zu setzen. (ns learnclojure) ; Weitere einfache Beispiele: ; str erstellt einen String aus allen Argumenten (str "Hallo" " " "Welt") ; => "Hallo Welt" ; Mathe ist einfach (+ 1 1) ; => 2 (- 2 1) ; => 1 (* 1 2) ; => 2 (/ 2 1) ; => 2 ; Gleichheit ist = (= 1 1) ; => true (= 2 1) ; => false ; Du brauchst auch not für Logik (not true) ; => false ; Verschachtelte Forms funktionieren, wie erwartet (+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2 ; Typen ;;;;;;;;;;;;; ; Clojure verwendet Javas Objekt Typen für Booleans, Strings und Zahlen. ; Verwende `class` um sie zu inszipieren. (class 1) ; Integer-Literale sind standardmäßig java.lang.Long (class 1.); Float-Literale sind java.lang.Double (class ""); Strings sind immer in doppelten Anführungszeichen notiert und sind java.lang.String (class false) ; Booleans sind java.lang.Boolean (class nil); Der "null" Wert heißt nil ; Wenn du ein literale Liste aus Daten erzeugen willst, verwendest du ' um ; zu verhindern dass es evaluiert wird '(+ 1 2) ; => (+ 1 2) ; (Kurzform für (quote (+ 1 2))) ; Du kannst eine zitierte Liste evaluieren (eval '(+ 1 2)) ; => 3 ; Kollektionen & Sequenzen ;;;;;;;;;;;;;;;;;;; ; Listen sind Linked-Lists Datenstrukturen, während Vektoren arraybasierend sind. ; Vektoren und Listen sind auch Java Klassen! (class [1 2 3]); => clojure.lang.PersistentVector (class '(1 2 3)); => clojure.lang.PersistentList ; Eine Liste würde nur als (1 2 3) geschrieben, aber wir müssen es zitieren ; damit der Leser aufhört zu denken, es sei eine Funktion. ; Außerdem ist (list 1 2 3) dasselbe, wie '(1 2 3) ; "Kollektioen" sind nur Gruppen von Daten ; Listen und Vektoren sind Kolllektionen: (coll? '(1 2 3)) ; => true (coll? [1 2 3]) ; => true ; "Sequenzen" (seqs) sind abstrakte Beschreibungen von Listen von Daten. ; Nur Listen sind seqs. (seq? '(1 2 3)) ; => true (seq? [1 2 3]) ; => false ; Ein seq muss nur einen Eintrittspunkt bereitstellen, wenn auf ihm zugegriffen wird. ; Das heißt, dass seqs faul sein können -- Mit ihnen kann man unendliche Serien beschreiben. (range 4) ; => (0 1 2 3) (range) ; => (0 1 2 3 4 ...) (eine unendliche Serie) (take 4 (range)) ; (0 1 2 3) ; Verwende cons um ein Item zum Anfang einer Liste oder eines Vektors hinzuzufügen. (cons 4 [1 2 3]) ; => (4 1 2 3) (cons 4 '(1 2 3)) ; => (4 1 2 3) ; Conj fügt ein Item auf die effizienteste Weise zu einer Kollektion hinzu. ; Für Listen fügt er sie an den Anfang hinzu. Für Vektoren fügt er sie an das Ende hinzu. (conj [1 2 3] 4) ; => [1 2 3 4] (conj '(1 2 3) 4) ; => (4 1 2 3) ; Verwende concat um Listen und Vektoren miteinander zu verbinden (concat [1 2] '(3 4)) ; => (1 2 3 4) ; Verwende filter, map um mit Kollektionen zu interagieren (map inc [1 2 3]) ; => (2 3 4) (filter even? [1 2 3]) ; => (2) ; Verwende reduce um sie zu reduzieren (reduce + [1 2 3 4]) ; = (+ (+ (+ 1 2) 3) 4) ; => 10 ; Reduce kann auch einen Initalwert als Argument verwenden (reduce conj [] '(3 2 1)) ; = (conj (conj (conj [] 3) 2) 1) ; => [3 2 1] ; Funktionen ;;;;;;;;;;;;;;;;;;;;; ; Verwende fn um neue Funktionen zu erstellen. Eine Funktion gibt immer ihr ; letztes Statement zurück. (fn [] "Hallo Welt") ; => fn ; (Du brauchst exta Klammern um sie aufzurufen) ((fn [] "Hallo Welt")) ; => "Hallo Welt" ; Du kannst eine Variable mit def erstellen (def x 1) x ; => 1 ; Weise eine Funktion einer Variable zu (def hello-world (fn [] "Hallo Welt")) (hello-world) ; => "Hallo Welt" ; Du kannst den Prozess verkürzen indem du defn verwendest (defn hello-world [] "Hallo Welt") ; [] ist die Liste der Argumente für die Funktion ; The [] is the list of arguments for the function. (defn hello [name] (str "Hallo " name)) (hello "Steve") ; => "Hallo Steve" ; Du kannst diese Kurzschreibweise verwenden um Funktionen zu erstellen: (def hello2 #(str "Hallo " %1)) (hello2 "Julie") ; => "Hallo Julie" ; Du kannst auch multi-variadische Funktionen haben (defn hello3 ([] "Hallo Welt") ([name] (str "Hallo " name))) (hello3 "Jake") ; => "Hallo Jake" (hello3) ; => "Hallo Welt" ; Funktionen können auch extra Argumente in einem seq für dich speichern (defn count-args [& args] (str "Du hast " (count args) " Argumente übergeben: " args)) (count-args 1 2 3) ; => "Du hast 3 Argumente übergeben: (1 2 3)" ; Du kannst reguläre und gepackte Argumente mischen (defn hello-count [name & args] (str "Hallo " name ", Du hast " (count args) " extra Argumente übergeben")) (hello-count "Finn" 1 2 3) ; => "Hallo Finn, Du hast 3 extra Argumente übergeben" ; Maps ;;;;;;;;;; ; Hash maps und Array maps teilen sich ein Interface. Hash maps haben eine schnellere Zugriffszeit, ; aber behalten keine Schlüsselreihenfolge. (class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap (class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap ; Arraymaps werden durch die meisten Operationen automatisch zu Hashmaps, ; sobald sie groß genug werden. Das heißt du musst dich nicht darum sorgen. ; Maps können einen beliebigen Hash-Typ als Schlüssel verwenden, in der Regel ; sind jedoch Keywords am besten. Keywords sind wie Strings, jedoch besitzen sie ; Performance-Vorteile (class :a) ; => clojure.lang.Keyword (def stringmap {"a" 1, "b" 2, "c" 3}) stringmap ; => {"a" 1, "b" 2, "c" 3} (def keymap {:a 1, :b 2, :c 3}) keymap ; => {:a 1, :c 3, :b 2} ; Übrigens werden Kommas als Leerzeichen behandelt und machen nichts. ; Rufe einen Wert von einer Map ab, indem du sie als Funktion aufrufst (stringmap "a") ; => 1 (keymap :a) ; => 1 ; Keywords können auch verwendet werden um ihren Wert aus der Map zu bekommen! (:b keymap) ; => 2 ; Versuche es nicht mit Strings. ;("a" stringmap) ; => Exception: java.lang.String cannot be cast to clojure.lang.IFn ; Das Abrufen eines nicht vorhandenen Keys gibt nil zurück (stringmap "d") ; => nil ; Verwende assoc um einen neuen Key zu einer Hash-map hinzuzufügen (def newkeymap (assoc keymap :d 4)) newkeymap ; => {:a 1, :b 2, :c 3, :d 4} ; Aber denk daran, Clojure Typen sind unveränderlich! keymap ; => {:a 1, :b 2, :c 3} ; Verwende dissoc um Keys zu entfernen (dissoc keymap :a :b) ; => {:c 3} ; Sets ;;;;;; (class #{1 2 3}) ; => clojure.lang.PersistentHashSet (set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3} ; Füge ein Element mit conj hinzu (conj #{1 2 3} 4) ; => #{1 2 3 4} ; Entferne ein Element mit disj (disj #{1 2 3} 1) ; => #{2 3} ; Teste auf Existenz, indem du das Set als Funktion verwendest: (#{1 2 3} 1) ; => 1 (#{1 2 3} 4) ; => nil ; Es gibt mehr Funktionen in dem clojure.sets Namespace. ; Nützliche Forms ;;;;;;;;;;;;;;;;; ; Logische Konstrukte in Clojure sind nur Makros und sie sehen, wie alles ; andere aus (if false "a" "b") ; => "b" (if false "a") ; => nil ; Verwende let um temporäre Bindungen aufzubauen (let [a 1 b 2] (> a b)) ; => false ; Gruppiere Statements mit do zusammen ; Group statements together with do (do (print "Hallo") "Welt") ; => "Welt" (prints "Hallo") ; Funktionen haben ein implizites do (defn print-and-say-hello [name] (print "Sage Hallo zu " name) (str "Hallo " name)) (print-and-say-hello "Jeff") ;=> "Hallo Jeff" (prints "Sage Hallo zu Jeff") ; let macht das auch (let [name "Urkel"] (print "Sage Hallo zu " name) (str "Hallo " name)) ; => "Hallo Urkel" (prints "Sage Hallo zu Urkel") ; Verwende die Threading Makros (-> and ->>) um Transformationen von ; Daten deutlicher auszudrücken. ; Das "Thread-zuerst" Makro (->) fügt in jede Form das Ergebnis des ; Vorherigen als erstes Argument (zweites Element) ein. (-> {:a 1 :b 2} (assoc :c 3) ;=> (assoc {:a 1 :b 2} :c 3) (dissoc :b)) ;=> (dissoc (assoc {:a 1 :b 2} :c 3) :b) ; Dieser Ausdruck kann auch als so geschrieben werden: ; (dissoc (assoc {:a 1 :b 2} :c 3) :b) ; and evaluates to {:a 1 :c 3} ; Der Doppelpfeil macht das Selbe, aber er fügt das Ergebnis von jeder ; Zeile an das Ende der Form, Das ist vor allem für Operationen auf ; Kollektionen nützlich: (->> (range 10) (map inc) ;=> (map inc (range 10) (filter odd?) ;=> (filter odd? (map inc (range 10)) (into [])) ;=> (into [] (filter odd? (map inc (range 10))) ; Result: [1 3 5 7 9] ; Wenn du in einer Situation bist, in der du mehr Freiheit willst, ; wohin du das Ergebnis vorheriger Datentransformationen in einem Ausdruck ; platzieren möchtest, kannst du das as-> Macro verwenden. Mit diesem Macro ; kannst du einen speziellen Namen auf die Ausgabe einer Transformationen geben. ; Du kannst es als Platzhalter in verketteten Ausdrücken verwenden: (as-> [1 2 3] input (map inc input);=> Du kannst die letzte Ausgabe der Transformation in der letzten Position verwenden (nth input 2) ;=> und auch in der zweiten Position, im selben Ausdruck verwenden (conj [4 5 6] input [8 9 10])) ;=> oder auch in der Mitte! ; Module ;;;;;;;;;;;;;;; ; Verwende "use" um alle Funktionen aus einem Modul zu bekommen (use 'clojure.set) ; Nun können wir set Operationen verwenden (intersection #{1 2 3} #{2 3 4}) ; => #{2 3} (difference #{1 2 3} #{2 3 4}) ; => #{1} ; Du kannst auch auswählen nur ein Subset von Funktionen zu importieren (use '[clojure.set :only [intersection]]) ; Verwende require um ein Modul zu importieren (require 'clojure.string) ; Verwende / um eine Funktion aus einem Modul aufzurufen ; Hier verwenden wir das Modul clojure.string und die Funktion blank? (clojure.string/blank? "") ; => true ; Du kannst auch einem Modul einen kürzeren Namen beim Import geben (require '[clojure.string :as str]) (str/replace "Das ist ein Test." #"[a-o]" str/upper-case) ; => "DAs IsT EIN TEsT." ; (#"" bezeichnet einen regulären literalen Ausdruck) ; Du kannst require aus einem Namespace verwenden (auch use ist möglich, aber nicht zu empfehlen) ; indem du :require verwendest. ; Du brauchst keine Zitierzeichen für deine Module verwenden, wenn du ; es auf diese Weise machst. (ns test (:require [clojure.string :as str] [clojure.set :as set])) ; Java ;;;;;;;;;;;;;;;;; ; Java hat eine riesige und nützliche Standardbibliothek, ; du möchtest lernen wie man sie verwendet. ; Verwende import um ein Java modul zu laden. (import java.util.Date) ; Du kannst auch von einem ns importieren. (ns test (:import java.util.Date java.util.Calendar)) ; Verwende den Klassennamen mit einem "." am Ende, um eine neue Instanz zu erstellen (Date.) ; ; Verwende . um Methoden aufzurufen oder verwende die ".method" Abkürzung (. (Date.) getTime) ; (.getTime (Date.)) ; Genau das Selbe ; Verwende / um statische Methoden aufzurufen (System/currentTimeMillis) ; (system ist immer da) ; Verwende doto um mit veränderliche Klassen besser umzugehen (import java.util.Calendar) (doto (Calendar/getInstance) (.set 2000 1 1 0 0 0) .getTime) ; => A Date. set to 2000-01-01 00:00:00 ; STM ;;;;;;;;;;;;;;;;; ; Software Transactional Memory ist der Mechanismus, den Clojure verwendet ; um mit persistenten Zuständen umzugehen. Es gibt ein Paar Konstrukte in ; Clojure die es verwenden. ; Ein Atom ist das Einfachste. Gebe es einen Initalwert (def my-atom (atom {})) ; Update ein Atom mit swap!. ; swap! nimmt eine Funktion und ruft sie mit dem aktuellen Zustand des ; Atoms auf und alle nachfolgenden Argumente als das Zweite (swap! my-atom assoc :a 1) ; Setzt my-atom zu dem Ergebnis von (assoc {} :a 1) (swap! my-atom assoc :b 2) ; Setzt my-atom zu dem Ergebnis von (assoc {:a 1} :b 2) ; Verwende '@' um das Atom zu dereferenzieren und den Wert zu bekommen my-atom ;=> Atom<#...> (Gibt das Atom Objekt zurück @my-atom ; => {:a 1 :b 2} ; Hier ist ein einfacher Zähler mit einem Atom (def counter (atom 0)) (defn inc-counter [] (swap! counter inc)) (inc-counter) (inc-counter) (inc-counter) (inc-counter) (inc-counter) @counter ; => 5 ; Andere STM Konstrukte sind refs und agents. ; Refs: http://clojure.org/refs ; Agents: http://clojure.org/agents