Überblick über die Entwicklung mit Clojure bei HEROLABS.
* Warum haben wir uns für Clojure entschieden? (Simplicity, Erweiterbarkeit, Java-Interop)
* Was heißt Funktionale Programmierung?
=> Man braucht ein anderes Mindset
* Was uns stört.
* Und wie entwickelt man mit Clojure (Ecosystem)?
Anlass war ein Talk bei mgm-tp.
2. About me
Promotion in KI - und ca. 10 Jahre
Erfahrung mit Common LISP
eCommerce/Finance mit
SinnerSchrader - in Java
Security / Visual Analytics bei Plath
Mobile Entertainment bei HEROLABS
- mit Clojure
3. HERO11
Mobile Sports Entertainment
„Beat the coach“, live während
reale Fußballspiele laufen
iOS Client, Clojure Server, MongoDB
Backend
Typische Aufgaben: Processing
eines Streams von Fußball-Events,
Fanout von Scoring an alle Nutzer
4. Warum Clojure?
Start „auf der grünen Wiese“: Freie Wahl der
Sprache
Scala? oder Clojure? oder Go?
Zwei Entwickler „pro Clojure“ (beide mit LISP-
Erfahrung), einer „pro Scala“
5. Was machen andere damit?
Twitter: big data processing (war Backtype:
Storm)
Flightcaster: realtime flight delay prediction
Runa: real-time targeted offers based on
statistical models (e-Commerce)
Prismatic: personal news aggregation
20. weitere Unterschiede
indexOfAny sucht nur in Strings, index-of-any durchsucht beliebige sequences
indexOfAny sucht nach einem Set von Characters, index-of-any kann beliebige
Prädikate verwenden
SIMPLICITY
21. weitere Unterschiede
indexOfAny sucht nur in Strings, index-of-any durchsucht beliebige sequences
indexOfAny sucht nach einem Set von Characters, index-of-any kann beliebige
Prädikate verwenden
indexOfAny liefert den ersten Treffer, index-filter liefert alle Treffer und kann mit
anderen Filtern kombiniert werden.
SIMPLICITY
22. weitere Unterschiede
indexOfAny sucht nur in Strings, index-of-any durchsucht beliebige sequences
indexOfAny sucht nach einem Set von Characters, index-of-any kann beliebige
Prädikate verwenden
indexOfAny liefert den ersten Treffer, index-filter liefert alle Treffer und kann mit
anderen Filtern kombiniert werden.
find the third occurrence of “heads” in a series of coin flips:
(nth (index-filter #{:h} [:t :t :h :t :h :t :t :t :h :h]) 2)
8
SIMPLICITY
23. weitere Unterschiede
indexOfAny sucht nur in Strings, index-of-any durchsucht beliebige sequences
indexOfAny sucht nach einem Set von Characters, index-of-any kann beliebige
Prädikate verwenden
indexOfAny liefert den ersten Treffer, index-filter liefert alle Treffer und kann mit
anderen Filtern kombiniert werden.
find the third occurrence of “heads” in a series of coin flips:
one,
(nth (index-filter #{:h} [:t :t :h :t :h :t :t :t :h :h]) 2)
pr
8
s er ror
er, les
si mpl gene ral
SIMPLICITY and more
24. lazy sequences
Natürlich werden in index-of-any nicht
erst alle Treffer erzeugt und dann alle bis
auf den ersten weggeworfen!
Siehe auch infinite sequences...
SIMPLICITY
25. for: Sequence comprehension
syntaktisches Konstrukt zum Erzeugen einer
Liste aus existierenden Listen
entlehnt aus mathematischer „set notation“
S = {2*x | x ∈ℕ, x² > 3}
SIMPLICITY
27. Die Sprache ist vollständig erweiterbar.
Erweiterungen sind vom eigentlichen
Sprachkern nicht zu unterscheiden.
UNSERE! Sprache
28. Beispiel aus dem Noir Webframework:
(defpage „/person/:id.html“ [id]
...)
UNSERE! Sprache
29. Beispiel aus Monger:
;; find scores 10 to 20
(with-collection "scores"
(find {})
(fields ,,, [:score :name])
(sort ,,, {:score -1})
(limit ,,, 10)
(skip ,,, 10))
UNSERE! Sprache
30. Hintergrund der Erweiterbarkeit
- Homoiconicity: Code is Data
- Das mächtigste Makrosystem:
Clojure verwenden, um Clojure zu erzeugen
„The whole language there all the time.“
P. Graham
UNSERE! Sprache
32. alle Java - Libraries stehen zur Verfügung
viele wichtige Libs mit clj-wrappern
interessante Libs in Clojure:
noir (Web Framework), avout (noch sehr beta),
congomongo bzw. monger (MongoDB)
Java-Interop
35. Funktionen als First-class
Objekte
Maximum einer Collection von Zahlen?
Wie würde man das in Java machen?
Funktional
36. Funktionen als First-class
Objekte
Maximum einer Collection von Zahlen?
Wie würde man das in Java machen?
If, Hilfsvariable, Schleife, ...
Funktional
38. Reduce
reduce
(reduce f coll)
(reduce f val coll)
f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
applying f to that result and the 3rd item, etc. If coll contains no
items, f must accept no arguments as well, and reduce returns the
result of calling f with no arguments. If coll has only 1 item, it
is returned and f is not called. If val is supplied, returns the
result of applying f to val and the first item in coll, then
applying f to that result and the 2nd item, etc. If coll contains no
items, returns val and f is not called.
Funktional
39. Sortieren
Sortieren einer Liste von Geboten nach :bid (invers) und :timestamp (ältestes zuerst)
(first
(sort-by
(juxt (comp (partial * -1) :bid)
:timestamp)
[{:id "af1" :bid 1 :timestamp 1}
{:id "ba3" :bid 12 :timestamp 3}
{:id "cd7" :bid 12 :timestamp 2}]))
Und ja, normalerweise machen wir das auch in der Datenbank ;)
Funktional
40. comp - Funktionskomposition
partial - partial function application (fixes
arguments)
juxt - juxtaposition of functions
(„Nebeneinanderstellung“)
Funktional
45. Map kann beliebige Elemente als Werte
enthalten (dynamische Typisierung):
{:first-name „Christian“,
:age 39,
:married true}
Domänenmodell
46. Maps werden oft herangezogen, um domain-
objects abzubilden.
Keine Klassen und Objekte, sondern generische
Maps.
Später evtl. ersetzt durch records (die dann
intern in Java-Klassen abgebildet werden).
Achtung: Lokalität! Wo erzeuge ich Objekte?
(encapsulation, ...)
Domänenmodell
47. Spring: Eine unserer Sorgen
Wir haben in Java-Projekten immer stark auf
Spring gesetzt. Wie macht man das in Clojure?
Inversion of Control
Dependency Injection
(um gegen Interface entwickeln zu können,
nicht konkrete Klassen)
49. Inversion of Control
Aber das ist doch genau funktionales Programmieren...
;; Create a word frequency map out of a large string s.
;; `s` is a long string containing a lot of words :)
(reduce #(assoc %1 %2 (inc (%1 %2 0)))
{}
(re-seq #"w+" s))
; (This can also be done using the `frequencies` function.)
Spring
51. Wo Licht ist …
oder
Was Java-Entwickler vermissen
52. Übersicht
Es gibt keine Klassendefinition, also nicht
zwingend eine „Übersicht“ darüber, wie
„Objekte“ aufgebaut sind.
Licht und SCHATTEN
53. Statische Typisierung
Natürlich bietet ein statisches Typsystem auch
Sicherheit - Typverletzungen erkennt schon der
Compiler.
Licht und SCHATTEN
54. Tooling
Die Tools sind noch nicht so ausgereift wie bei
Java. Insbesondere Refactoring Tools vermissen
wir manchmal.
Licht und SCHATTEN
55. Unsere „Lösung“
- automatisierte Tests
(braucht man auch in Java)
- Dokumentation
- Validierungsfunktionen
(braucht man sowieso)
Licht und SCHATTEN
59. REPL und Tests
Interaktive Entwicklung von Funktionen in der REPL
Erhöht die Testbarkeit, weil man in der REPL ja nicht immer das gesamte System
hochfahren möchte.
Wenn man mit einem ersten Draft zufrieden ist, dann macht man Tests daraus,
verfeinert diese und verbessert die Funktion.
Das Ergebnis ist eine Funktion und ein Satz von Unit-Tests
Mein Liebling in schwierigen Fällen: (debug-repl)
https://github.com/GeorgeJahad/debug-repl
60. Das Clojure Ecosystem
Projektmanagement: lein (maven wie es sein sollte)
Entwicklungsumgebung: Emacs, IntelliJ, Eclipse
Testframework: midje
Web-Framework: Noir
Hosting: Erst Heroku, jetzt AWS, Automatisierung mit
Jenkins, Pallet
61. Hacks explained: apropos+
(ns dev.apropos)
(defn apropos+
"Given a regular expression or stringable thing, return a seq of all definitions in all currently-loaded
namespaces that match the str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str (key %)))
#(.contains (str (key %)) (str str-or-pattern)))]
(for [ns (all-ns)
public (ns-publics ns)
:when (matches? public)]
(second public))))
;; (in-ns 'user)
;; (use 'dev.apropos)
;; (apropos+ "*warn")
62. Hacks explained: apropos+
(ns dev.apropos)
(defn apropos+
"Given a regular expression or stringable thing, return a seq of all definitions in all currently-loaded
namespaces that match the str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str (key %)))
#(.contains (str (key %)) (str str-or-pattern)))]
(for [ns (all-ns)
public (ns-publics ns)
:when (matches? public)]
(second public))))
;; (in-ns 'user)
;; (use 'dev.apropos)
;; (apropos+ "*warn")
63. Hacks explained: apropos+
(ns dev.apropos)
(defn apropos+
"Given a regular expression or stringable thing, return a seq of all definitions in all currently-loaded
namespaces that match the str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str (key %)))
#(.contains (str (key %)) (str str-or-pattern)))]
(for [ns (all-ns)
public (ns-publics ns)
:when (matches? public)]
(second public))))
;; (in-ns 'user)
;; (use 'dev.apropos)
;; (apropos+ "*warn")
64. Hacks explained: apropos+
(ns dev.apropos)
(defn apropos+
"Given a regular expression or stringable thing, return a seq of all definitions in all currently-loaded
namespaces that match the str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str (key %)))
#(.contains (str (key %)) (str str-or-pattern)))]
(for [ns (all-ns)
public (ns-publics ns)
:when (matches? public)]
(second public))))
;; (in-ns 'user)
;; (use 'dev.apropos)
;; (apropos+ "*warn")
65. Hacks explained: apropos+
(ns dev.apropos)
(defn apropos+
"Given a regular expression or stringable thing, return a seq of all definitions in all currently-loaded
namespaces that match the str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str (key %)))
#(.contains (str (key %)) (str str-or-pattern)))]
(for [ns (all-ns)
public (ns-publics ns)
:when (matches? public)]
(second public))))
;; (in-ns 'user)
;; (use 'dev.apropos)
;; (apropos+ "*warn")
68. Insgesamt: Super-zufrieden.
Aber: Es gab natürlich Probleme in der Adaption.
Änderung der Denke notwendig... Arbeit ohne "mal
schnell eine Variable hochzählen" ist nicht immer
einfach.
Weniger Code (d.h. weniger zu lesen und zu warten,
was 90% der Arbeit ausmacht)
More fun - just dive into your program using the REPL