Java Interop |
Clojure läuft auf der JVM
Für viele Funktionalitäten gibt es bereits gute Java Bibliotheken:
Wir können jede Java Klasse direkt verwenden.
Die Clojure Datentypen sind unter der Haube als Java Klassen implementiert.
Die Member Methoden einer Klasse können wir direkt aufrufen:
(.toUpperCase "hello world")
Statische Methoden und Felder einer Java Klasse können wir auch direkt aufrufen:
(println (java.lang.Math/abs -3))
(println (java.lang.Math/PI))
Wir können neue Objekte auf zwei Arten erzeugen:
(def d (java.util.Date. 1685016589000))
(def d (new java.util.Date 1685016589000))
Java Objekte bringen üblicherweise Mutability mit:
(def stack (java.util.Stack.))
(.push stack "a")
(.push stack 1)
(.push stack :foo)
(println (.pop stack))
Java Container sind nicht wirklich stark typisiert.
Mit doto können wir eine ähnliche Semantik wie Threading darstellen:
(def stack (doto (java.util.Stack.)
(.push "a")
(.push 1)
(.push :foo)))
(println (.pop stack))
Für Java Klassen gibt es ein import statement, welches analog zu dem Java Äquivalent funktioniert:
(import [java.util Date Stack])
(def stack (doto (Stack.)
(.push "a")))
Meistens wollen wir aber (analog zu require) die Java Imports als Teil der Namespace Definition haben:
(ns my-namespace
(:import [java.util Date Stack]))
(def stack (doto (Stack.)
(.push "a")))
Wir können Java Libraries analog zu Clojure Libraries verwenden.
Maven Central gibt uns auch direkt das passende Leiningen Snippet.
Es ist auch möglich in Clojure Klassen zu erstellen, die dann in Java genutzt werden können.
Stichworte:
gen-classdefrecordDer übliche Weg ist eher, dass wir Java von Clojure aus aufrufen
Einige fundamentale Java Klassen finden recht häufig Verwendung
Systemjava.util.Datejava.io.File, java.io.BufferedReader, etc.System
(System/getenv)
; => Map of environment variables
(System/getProperties)
; => Map of system properties
(Sytstem/nanoTime)
; => Current JVM time
(System/exit)
; => quit process
Wenn wir Dateien lesen oder schreiben wollen, brauchen wir oft die Java Reader oder Writer Klassen
java.io.Filejava.io.BufferedReaderjava.io.FileReaderjava.io.BufferedWriterjava.io.FileWriterLesen von Dateieigenschaften:
(let [file (java.io.File. "/")]
(println (.exists file))
(println (.canWrite file))
(println (.getPath file)))
String $\Rightarrow$ File und File $\Rightarrow$ String sind einfach:
(spit "/tmp/foo.txt" "Some text")
(slurp "/tmp/foo.txt")
spit und slurp nehmen als Input eine beliebige
schreib- oder lesbare Ressource (oder konvertieren zu einer).
(let [s (java.io.StringWriter.)]
(spit s "Lorem ipsum")
(.toString s))
(let [s (java.io.StringReader. "Lorem ipsum")]
(slurp s))
Mit dem with-open Macro stellen wir sicher, dass die Ressource wieder geschlossen wird.
(with-open [file (-> "/tmp/foo.txt"
java.io.FileReader.
java.io.BufferedReader.)]
(println (first (line-seq file))))
Einen einfachen BufferedReader aus einer Datei
können wir auch mit clojure.java.io/reader erstellen:
(with-open [file (clojure.java.io/reader "/tmp/foo.txt")]
(println (first (line-seq file))))
line-seq erstellt eine lazy sequence der Zeilen
der Datei.
Exkurs: Lazy Sequences
Eine lazy sequence ist eine Liste, deren Werte noch nicht realisiert sind.
Idee: Berechne die Werte erst, wenn sie auch gebraucht werden.
Eine Lazy Sequence können wir erzeugen, indem wir angeben, wie der nächste Wert rekursiv erzeugt werden kann:
(defn fib
([]
(fib 1 1))
([a b]
(println "Generating fib" a)
(lazy-seq (cons a (fib b (+ a b))))))
(take 30 (fib))
Eine Lazy Sequence kann unendlich lang sein.
Die Ergebnisse werden nur einmal berechnet und dann gespeichert.
(defn fib
([]
(fib 1 1))
([a b]
(lazy-seq (cons (do (println "Generating fib" a) a)
(fib b (+ a b))))))
(def fib-10 (take 10 (fib)))
(println fib-10)
(println fib-10)
Viele Standardfunktionen arbeiten mit Lazy Sequences
(->> (range)
(map #(* % %))
(filter #(= 1 (mod % 2)))
(drop 10)
(take 20))
Analog zu Streams oder Generators in anderen Sprachen
Eine sehr große Lazy Sequence ist Memory effizient, wenn wir sie nur einmal verwenden.
(let [s (map #(* % 1234567) (range 1e8))]
(println (count s))
(println (count s)))
vs
(let [s (map #(* % 1234567) (range 1e8))]
(println (count s)))
Wenn wir keine Lazy Seq haben wollen können wir sie umwandeln
(->> (range 10)
(map #(* % %))
(vec))
oder eine Funktion nehmen, die direkt einen Vektor erzeugt
(mapv #(* % %) (range 10))