73
CHANGE YOUR MINDSET Funktionales Programmieren mit Clojure chris_betz xing.to/betz

Funktionales Programmieren mit Clojure

Embed Size (px)

DESCRIPTION

Ü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.

Citation preview

Page 1: Funktionales Programmieren mit Clojure

CHANGE YOUR MINDSETFunktionales Programmieren mit Clojure

chris_betzxing.to/betz

Page 2: Funktionales Programmieren mit Clojure

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

About me

Page 3: Funktionales Programmieren mit Clojure

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

HERO11

Page 4: Funktionales Programmieren mit Clojure

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“

Page 5: Funktionales Programmieren mit Clojure

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

Page 6: Funktionales Programmieren mit Clojure

Was uns fasziniert...

Page 7: Funktionales Programmieren mit Clojure

SIMPLICITY

Page 8: Funktionales Programmieren mit Clojure

Reduktion aufs Wesentliche

Java:class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); }}

Clojure:(println "Hello World!")

SIMPLICITY

Page 9: Funktionales Programmieren mit Clojure

Erweitertes Beispiel,aus : Programming Clojure, Stuart Halloway

SIMPLICITY

Page 10: Funktionales Programmieren mit Clojure

org.apache.commons.lang.StringUtils:

indexOfAny - findet in einem String das erste Vorkommen eines Zeichens aus einer vorgegebenen Menge

StringUtils.indexOfAny(null, *) = -1StringUtils.indexOfAny("", *) = -1StringUtils.indexOfAny(*, null) = -1StringUtils.indexOfAny(*, []) = -1StringUtils.indexOfAny("zzabyycdxx",['z','a']) = 0StringUtils.indexOfAny("zzabyycdxx",['b','y']) = 3StringUtils.indexOfAny("aba", ['z']) = -1

SIMPLICITY

Page 11: Funktionales Programmieren mit Clojure

public static int indexOfAny(String str, char[] searchChars) { if (isEmpty(str) || ArrayUtils.isEmpty(searchChars)) { return -1; } for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); for (int j = 0; j < searchChars.length; j++) { if (searchChars[j] == ch) { return i; } return -1; } }}

SIMPLICITY

Page 12: Funktionales Programmieren mit Clojure

Wie macht man das in Clojure?

SIMPLICITY

Page 13: Funktionales Programmieren mit Clojure

(defn indexed [coll] (map vector (iterate inc 0) coll))

(indexed "abcde")⇒ ([0 \a] [1 \b] [2 \c] [3 \d] [4 \e])

SIMPLICITY

Page 14: Funktionales Programmieren mit Clojure

(defn index-filter [pred coll] (when pred (for [[idx elt] (indexed coll) :when (pred elt)] idx)))

(index-filter #{\a \b} "abcdbbb")⇒ (0 1 4 5 6)

(index-filter #{\a \b} "xyz")⇒ ()

SIMPLICITY

Page 15: Funktionales Programmieren mit Clojure

(defn index-of-any [pred coll] (first (index-filter pred coll)))

(index-of-any #{\z \a} "zzabyycdxx")⇒0

(index-of-any #{\b \y} "zzabyycdxx")⇒3

SIMPLICITY

Page 16: Funktionales Programmieren mit Clojure

(defn indexed [coll] (map vector (iterate inc 0) coll))

(defn index-filter [pred coll] (when pred (for [[idx elt] (indexed coll) :when (pred elt)] idx)))

(defn index-of-any [pred coll] (first (index-filter pred coll)))

SIMPLICITY

Page 17: Funktionales Programmieren mit Clojure

Simplicity

Metrik LOC Branches Exits/Method Variables

Java imperativ 14 4 3 3

Clojurefunktional 6 1 1 0

SIMPLICITY

Page 18: Funktionales Programmieren mit Clojure

weitere Unterschiede

SIMPLICITY

Page 19: Funktionales Programmieren mit Clojure

weitere Unterschiede

indexOfAny sucht nur in Strings, index-of-any durchsucht beliebige sequences

SIMPLICITY

Page 20: Funktionales Programmieren mit Clojure

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

Page 21: Funktionales Programmieren mit Clojure

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

Page 22: Funktionales Programmieren mit Clojure

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

Page 23: Funktionales Programmieren mit Clojure

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

simpler, l

ess error

prone,

and more g

eneral

SIMPLICITY

Page 24: Funktionales Programmieren mit Clojure

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

Page 25: Funktionales Programmieren mit Clojure

for: Sequence comprehension

syntaktisches Konstrukt zum Erzeugen einer Liste aus existierenden Listen

entlehnt aus mathematischer „set notation“

S = {2*x | x ∈ℕ, x² > 3}

SIMPLICITY

Page 26: Funktionales Programmieren mit Clojure

Unsere! Sprache

Page 27: Funktionales Programmieren mit Clojure

Die Sprache ist vollständig erweiterbar.

Erweiterungen sind vom eigentlichen Sprachkern nicht zu unterscheiden.

UNSERE! Sprache

Page 28: Funktionales Programmieren mit Clojure

Beispiel aus dem Noir Webframework:

(defpage „/person/:id.html“ [id]

...)

UNSERE! Sprache

Page 29: Funktionales Programmieren mit Clojure

Beispiel aus Monger:

;; find scores 10 to 20(with-collection "scores"  (find {})  (fields ,,, [:score :name])  (sort ,,, {:score -1})  (limit ,,, 10)  (skip ,,, 10))

UNSERE! Sprache

Page 30: Funktionales Programmieren mit Clojure

- 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

Hintergrund der Erweiterbarkeit

Page 31: Funktionales Programmieren mit Clojure

Java-Interop

Page 32: Funktionales Programmieren mit Clojure

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

Page 33: Funktionales Programmieren mit Clojure

Aber:

Man braucht ein anderes Mindset

Page 34: Funktionales Programmieren mit Clojure

Funktional- was heisst

das?

Page 35: Funktionales Programmieren mit Clojure

Funktionen als First-class Objekte

Maximum einer Collection von Zahlen?

Wie würde man das in Java machen?

Funktional

Page 36: Funktionales Programmieren mit Clojure

Funktionen als First-class Objekte

Maximum einer Collection von Zahlen?

Wie würde man das in Java machen?

If, Hilfsvariable, Schleife, ...

Funktional

Page 37: Funktionales Programmieren mit Clojure

(reduce max [35 62 -12 43 56 7])

Funktional

Page 38: Funktionales Programmieren mit Clojure

Reducereduce

(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, thenapplying f to that result and the 3rd item, etc. If coll contains noitems, f must accept no arguments as well, and reduce returns theresult of calling f with no arguments. If coll has only 1 item, itis returned and f is not called. If val is supplied, returns theresult of applying f to val and the first item in coll, thenapplying f to that result and the 2nd item, etc. If coll contains noitems, returns val and f is not called.

Funktional

Page 39: Funktionales Programmieren mit Clojure

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

Page 40: Funktionales Programmieren mit Clojure

comp - Funktionskomposition

partial - partial function application (fixes arguments)

juxt - juxtaposition of functions („Nebeneinanderstellung“)

Funktional

Page 41: Funktionales Programmieren mit Clojure

Fibonacci:

In Java...

Funktional

Page 42: Funktionales Programmieren mit Clojure

In Clojure - trivial:

(defn fibonacci [a] (cond (> a 1) (+ (fibonacci (- a 2)) (fibonacci (- a 1))) (= a 1) 1 (= a 0) 0)

Funktional

Page 43: Funktionales Programmieren mit Clojure

In Clojure - clojure style

(def fib-seq ((fn rfib [a b] (lazy-seq (cons a (rfib b (+ a b))))) 0 1))

(take 20 fib-seq)

Funktional

Page 44: Funktionales Programmieren mit Clojure

Domänenmodell

Page 45: Funktionales Programmieren mit Clojure

Map kann beliebige Elemente als Werte enthalten (dynamische Typisierung):

{:first-name „Christian“, :age 39, :married true}

Domänenmodell

Page 46: Funktionales Programmieren mit Clojure

Maps werden o$ 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

Page 47: Funktionales Programmieren mit Clojure

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)

Page 48: Funktionales Programmieren mit Clojure

Inversion of Control

Reusable code controls the execution of problem-specific code

Spring

Page 49: Funktionales Programmieren mit Clojure

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

Page 50: Funktionales Programmieren mit Clojure

Dependency Injection

Zentral(declare create-person)

Im „Nutzcode“

(create-person „Christian“ „Betz“ :age 39)

In der Definition

(defn create-person [first-name last-name ...] ...)

Spring

Page 51: Funktionales Programmieren mit Clojure

Wo Licht ist …oder

Was Java-Entwickler vermissen

Page 52: Funktionales Programmieren mit Clojure

Übersicht

Es gibt keine Klassendefinition, also nicht zwingend eine „Übersicht“ darüber, wie „Objekte“ aufgebaut sind.

Licht und SCHATTEN

Page 53: Funktionales Programmieren mit Clojure

Statische Typisierung

Natürlich bietet ein statisches Typsystem auch Sicherheit - Typverletzungen erkennt schon der Compiler.

Licht und SCHATTEN

Page 54: Funktionales Programmieren mit Clojure

Tooling

Die Tools sind noch nicht so ausgerei$ wie bei Java. Insbesondere Refactoring Tools vermissen wir manchmal.

Licht und SCHATTEN

Page 55: Funktionales Programmieren mit Clojure

Unsere „Lösung“

- automatisierte Tests(braucht man auch in Java)

- Dokumentation

- Validierungsfunktionen(braucht man sowieso)

Licht und SCHATTEN

Page 56: Funktionales Programmieren mit Clojure

Und wie entwickelt man?

Page 57: Funktionales Programmieren mit Clojure

Test-driven development?

Page 58: Funktionales Programmieren mit Clojure

REPL-driven development!

Page 59: Funktionales Programmieren mit Clojure

REPL und TestsInteraktive 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 Dra# 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

Page 60: Funktionales Programmieren mit Clojure

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

Page 61: Funktionales Programmieren mit Clojure

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")

Page 62: Funktionales Programmieren mit Clojure

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")

Page 63: Funktionales Programmieren mit Clojure

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")

Page 64: Funktionales Programmieren mit Clojure

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")

Page 65: Funktionales Programmieren mit Clojure

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")

Page 66: Funktionales Programmieren mit Clojure

user=> (apropos+ "*warn")

(#'clojure.core/*warn-on-reflection*)

user=> (apropos+ "format")

(#'clojure.pprint/formatter-out #'clojure.pprint/formatter #'clojure.pprint/cl-format #'clojure.core/format)

Page 67: Funktionales Programmieren mit Clojure

Recap

Page 68: Funktionales Programmieren mit Clojure

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

Page 69: Funktionales Programmieren mit Clojure

Clojure-Dojo?

Page 70: Funktionales Programmieren mit Clojure

lein (https://github.com/technomancy/leiningen)

~/.lein/profiles.clj:{:user {:plugins [[lein-midje "2.0.0-SNAPSHOT"][lein-noir "1.2.1"]]}}

noir (http://webnoir.org)

foundation: (http://foundation.zurb.com/)

Branch: http://branch.com/featured

-> lein new noir gwitter

http://localhost:8080/

Page 71: Funktionales Programmieren mit Clojure

Mehr Infos...?

Page 72: Funktionales Programmieren mit Clojure

Full Disclojure (Vimeo)http://vimeo.com/channels/fulldisclojure/videos

Clojure - Functional Programming for the JVMhttp://java.ociweb.com/mark/clojure/article.html

Clojure Docshttp://clojuredocs.org/

Closure/West Conference (Slides in github)http://clojurewest.org

@planetclojure, @fogus, @stuarthalloway, ...

Page 73: Funktionales Programmieren mit Clojure

Danke !

Images:http://www.flickr.com/photos/marcp_dmoz/4138211812http://www.flickr.com/photos/b-natix/5249425587http://www.flickr.com/photos/stuckincustoms/4208255182http://www.flickr.com/photos/abbyladybug/491270355http://www.flickr.com/photos/petereed/496392956/http://www.flickr.com/photos/visualpanic/2394430691http://www.flickr.com/photos/bitzcelt/2762928930http://www.flickr.com/photos/tobstone/84696661http://www.flickr.com/photos/pulpolux/1306334352/http://www.flickr.com/photos/yashna13/5101434185/http://www.flickr.com/photos/go_freyer/3185104403http://www.flickr.com/photos/seier/4797164691/http://www.flickr.com/photos/visualpanic/2512530843http://www.flickr.com/photos/zeze57/5717675695