Slide 1

Slide 1 text

Clojure for OOP folks Stefan Tilkov | @stilkov | innoQ http://xkcd.com/297/

Slide 2

Slide 2 text

© 2012 innoQ Deutschland GmbH OOP Thinking model domains with classes & interfaces encapsulate data in objects prefer speci c over generic solutions explicitly provide for generic access

Slide 3

Slide 3 text

© 2012 innoQ Deutschland GmbH http://upload.wikimedia.org/wikip

Slide 4

Slide 4 text

© 2011 innoQ Deutschland GmbH Clojure http://upload.wikimedia.org/wikipedia/en/1/1a/Clo A practical Lisp variant for the JVM Functional programming Dynamic Typing Full-featured macro system Concurrent programming support Bi-directional Java interop Immutable persistent data structures

Slide 5

Slide 5 text

© 2012 innoQ Deutschland GmbH http://www.tbray.org/ongoing/When/200x/2008/09/25/-big/R0010774.jpg.html Rich Hickey

Slide 6

Slide 6 text

© 2011 innoQ Deutschland GmbH Data structures Numbers 2 3 4 0.234 3/5 -2398989892820093093090292321 Strings "Hello" "World" Characters \a \b \c Keywords :first :last Symbols a b c Regexps #"Ch.*se" Lists (a b c) ((:first :last "Str" 3) (a b)) Vectors [2 4 6 9 23] [2 4 6 [8 9] [10 11] 9 23] Maps {:de "Deutschland", :fr "France"} Sets #{"Bread" "Cheese" "Wine"}

Slide 7

Slide 7 text

© 2011 innoQ Deutschland GmbH Syntax

Slide 8

Slide 8 text

© 2012 innoQ Deutschland GmbH “You’ve just seen it” – Rich Hickey

Slide 9

Slide 9 text

© 2011 innoQ Deutschland GmbH Syntax (def my-set #{:a :b :c :c :c}) ;; #{:a :b :c} (def v [2 4 6 9 23]) (v 0) ;; 2 (v 2) ;; 6 (def people {:pg "Phillip", :st "Stefan"}) (people :st) ;; "Stefan" (:pg people) ;; "Phillip" (:xyz people) ;; nil (+ 2 2) ;; 4 (+ 2 3 5 4) ;; 14 (class (/ 4 3)) ;; clojure.lang.Ratio (* (/ 4 3) 3) ;; 4 (format "Hello, %s # %d" "world" 1)

Slide 10

Slide 10 text

© 2011 innoQ Deutschland GmbH Syntax ; (a 2 3) (quote (a 2 3)) ;; (a 2 3) '(a 2 3) ;; (a 2 3) ; Evaluation (eval '(format "Hello, %s" "World")) (eval (read-string "(+ 2 2)")) (format "Hello, %s # %d" "world" 1) ; "Hello, World # 1" (apply format ["Hello, %s # %d" "world" 1])

Slide 11

Slide 11 text

© 2011 innoQ Deutschland GmbH Functions (fn [x] (format "The value is %s\n" x)) ;; user$eval__1706$fn__1707@390b755d ((fn [x] (format "The value is %s\n" x)) "Hello") ;; "The value is Hello" (def testfn (fn [x] (format "The value is %s\n" x))) (testfn "Hello") (defn testfn [x] (format "The value is %s\n" x)) (testfn "Hello")

Slide 12

Slide 12 text

© 2011 innoQ Deutschland GmbH Functions (defn even [x] (= 0 (rem x 2))) (even 4) ;; true (def even-alias even) (even-alias 2) ;; true (defn every-even? [l] (every? even l)) (every-even? '(2 4 6 8 9)) ;; false (every? #(= 0 (rem % 2)) '(2 4 6 8 9)) ;; false

Slide 13

Slide 13 text

© 2011 innoQ Deutschland GmbH Closures (defn make-counter [initial-value] (let [current-value (atom initial-value)] (fn [] (swap! current-value inc)))) (def counter1 (make-counter 0)) (counter1) ;; 1 (counter1) ;; 2 (def counter2 (make-counter 17)) (counter1) ;; 3 (counter2) ;; 18 (counter1) ;; 4 (counter2) ;; 19

Slide 14

Slide 14 text

© 2011 innoQ Deutschland GmbH Recursion (defn reduce-1 [f val coll] (if (empty? coll) val (reduce-1 f (f val (first coll)) (rest coll)))) (reduce-1 + 0 [1 2 3 4]) ;; 10 (reduce-1 + 0 (range 5)) ;; 10 (reduce-1 + 0 (range 50)) ;; 1225 (reduce-1 + 0 (range 50000)) ;; java.lang.StackOverflowError

Slide 15

Slide 15 text

© 2011 innoQ Deutschland GmbH (defn reduce-2 [f val coll] (if (empty? coll) val (recur f (f val (first coll)) (rest coll)))) (defn reduce-1 [f val coll] (if (empty? coll) val (reduce-1 f (f val (first coll)) (rest coll)))) Recursion (reduce-2 + 0 [1 2 3 4]) ;; 10 (reduce-2 + 0 (range 5)) ;; 10 (reduce-2 + 0 (range 50)) ;; 1225 (reduce-2 + 0 (range 50000)) ;; 1249975000

Slide 16

Slide 16 text

© 2011 innoQ Deutschland GmbH Example (ns sample.grep "A simple complete Clojure program." (:use [clojure.contrib.io :only [read-lines]]) (:gen-class)) (defn numbered-lines [lines] (map vector (iterate inc 0) lines)) (defn grep-in-file [pattern file] {file (filter #(re-find pattern (second %)) (numbered-lines (read-lines file)))}) (defn grep-in-files [pattern files] (apply merge (map #(grep-in-file pattern %) files))) (defn print-matches [matches] (doseq [[fname submatches] matches, [line-no, match] submatches] (println (str fname ":" line-no ":" match)))) (defn -main [pattern & files] (if (or (nil? pattern) (empty? files)) (println "Usage: grep ") (do (println (format "grep started with pattern %s and file(s) %s" pattern (apply str (interpose ", " files)))) (print-matches (grep-in-files (re-pattern pattern) files)) (println "Done."))))

Slide 17

Slide 17 text

© 2011 innoQ Deutschland GmbH Lots of other cool stu ‣ Persistent data structures ‣ Sequences ‣ Support for concurrent programming ‣ Destructuring ‣ List comprehensions ‣ Metadata ‣ Optiional type information ‣ Multimethods ‣ Pre & Post Conditions ‣ Records/Protocols ‣ Extensive core and contrib libraries ‣ …

Slide 18

Slide 18 text

© 2012 innoQ Deutschland GmbH Syntax Idioms

Slide 19

Slide 19 text

© 2012 innoQ Deutschland GmbH OOP Thinking model domains with classes & interfaces encapsulate data in objects prefer speci c over generic solutions explicitly provide for generic access

Slide 20

Slide 20 text

© 2012 innoQ Deutschland GmbH Namespaces

Slide 21

Slide 21 text

© 2012 innoQ Deutschland GmbH ... just like Java packages

Slide 22

Slide 22 text

© 2012 innoQ Deutschland GmbH require: (re-)load libs :reload, :reload-all, :as refer: import names :exclude [], :only [], :rename {…:…} use: require + refer :exclude [], :only [], :rename {…:…} ns: create namespace :require, :refer, :use, :gen-class Handle var name clashes Reduce dependencies Dynamic reloading Namespace aliases Convenient REPL usage Flexible handling in sources Provide encapsulation

Slide 23

Slide 23 text

© 2012 innoQ Deutschland GmbH require: (re-)load libs :reload, :reload-all, :as, :refer refer: import names :exclude [], :only [], :rename {…:…} ns: create namespace :require, :refer, :use, :gen-class Handle var name clashes Reduce dependencies Dynamic reloading Namespace aliases Convenient REPL usage Flexible handling in sources Provide encapsulation

Slide 24

Slide 24 text

© 2012 innoQ Deutschland GmbH (defn ...) (defmacro ...) (defmulti ...) (defmethod ...) (defn- ...) (def ^:private ...) (def ^:dynamic ...) (ns com.example.some-ns "Well-documented ns" (:use [com.example.n1 :only [xyz]]) (:require [com.example.ns2 :as n2]))

Slide 25

Slide 25 text

© 2012 innoQ Deutschland GmbH Data

Slide 26

Slide 26 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects public class Point { private final double x; private final double y; public Point(double x, double y) { this.x = x; this.y = y; } } Point p1 = new Point(3, 4); (def p1 [3 4])

Slide 27

Slide 27 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects (def p1 [3 4]) Immutable Reusable Compatible

Slide 28

Slide 28 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects import static java.lang.Math.sqrt; public class Point { private final double x; private final double y; public Point(double x, double y) { this.x = x; this.y = y; } public double distanceTo(Point other) { double c1 = other.x - this.x; double c2 = other.y - this.y; return sqrt(c1 * c1 + c2 * c2); } }

Slide 29

Slide 29 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects (import-static java.lang.Math sqrt) (defn distance [[x1 y1] [x2 y2]] (let [c1 (- x2 x1) c2 (- y2 y1)] (sqrt (+ (* c1 c1) (* c2 c2)))))

Slide 30

Slide 30 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects (defn rand-seq [limit] (repeatedly #(rand-int limit))) (take 10 (partition 2 (rand-seq 10))) in nite randoms pairs of random ints 10 random points ;((3 6) (6 1) (8 5) (0 7) (3 8) (0 6) (1 6) (7 6) (0 1) (8 9))

Slide 31

Slide 31 text

© 2012 innoQ Deutschland GmbH Data structures vs. objects (defn circumference [vertices] (reduce + (map distance vertices (drop 1 (cycle vertices))))) in nite repetition seq without rst all ;((3 6) (6 1) (8 5) (0 7) (3 8) (0 6) (1 6) (7 6) (0 1) (8 9)) ;((6 1) (8 5) (0 7) (3 8) (0 6) (1 6) (7 6) (0 1) (8 9) (3 6)) ;58.06411369758525 ...

Slide 32

Slide 32 text

© 2012 innoQ Deutschland GmbH assoc assoc-in butlast concat conj cons count cycle difference dissoc distinct distinct? drop-last empty empty? every? filter first flatten group-by interleave interpose intersection into join lazy-cat mapcat merge merge-with not-any? not-empty? not-every? nth partition partition-all partition-by peek pop popy project remove replace rest rseq select select-keys shuffle some split-at split-with subvec take take-last take-nth take-while union update-in

Slide 33

Slide 33 text

© 2012 innoQ Deutschland GmbH app request response objects object

Slide 34

Slide 34 text

© 2012 innoQ Deutschland GmbH public interface Servlet { void init(ServletConfig servletConfig) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; String getServletInfo(); void destroy(); }

Slide 35

Slide 35 text

© 2012 innoQ Deutschland GmbH public interface HttpServletRequest extends ServletRequest { public String getAuthType(); public Cookie[] getCookies(); public Enumeration getHeaders(String name); public Enumeration getHeaderNames(); public String getMethod(); public String getQueryString(); public String getRemoteUser(); public HttpSession getSession(boolean create); public boolean authenticate(HttpServletResponse response) throws IOException,ServletException; public void login(String username, String password) throws ServletException; ...

Slide 36

Slide 36 text

© 2012 innoQ Deutschland GmbH public interface HttpServletResponse extends ServletResponse { public void addCookie(Cookie cookie); public boolean containsHeader(String name); public void sendError(int sc, String msg) throws IOException; public void sendRedirect(String location) throws IOException; public void setDateHeader(String name, long date); public void addDateHeader(String name, long date); public void setHeader(String name, String value); public void addHeader(String name, String value); public void setStatus(int sc); public int getStatus(); ...

Slide 37

Slide 37 text

© 2012 innoQ Deutschland GmbH app request response data function

Slide 38

Slide 38 text

© 2012 innoQ Deutschland GmbH (defn hello-world-app [req] {:status 200 :headers {"Content-Type" "text/plain"} :body "Hello, World!"}) (hello-world-app {:uri "/foo" :request-method :get}) > {...} (run-jetty hello-world-app {:port 8080})

Slide 39

Slide 39 text

© 2012 innoQ Deutschland GmbH Maps (def projects #{{:id "1", :kind :time-material, :description "Consulting for BigCo", :budget 25000, :team [:joe, :chuck, :james]} {:id "2", :kind :fixed-price, :description "Development for Startup", :budget 100000, :team [:john, :chuck, :james, :bill]} {:id "3", :kind :fixed-price, :description "Clojure Training", :budget 3000, :team [:joe, :john]}})

Slide 40

Slide 40 text

© 2012 innoQ Deutschland GmbH Map access (defn all-members [projects] (reduce conj #{} (flatten (map :team projects)))) seq of vectors seq of members with duplicates set of all team members ;#{:chuck :joe :james :john :bill} (all-members projects)

Slide 41

Slide 41 text

© 2012 innoQ Deutschland GmbH Map access & coupling (defn all-members [projects] (reduce conj #{} (flatten (map :team projects)))) #{{:id "2", :kind :fixed-price, :description "Development for Startup", :budget 100000, :team [:john, :chuck, :james, :bill]}}

Slide 42

Slide 42 text

© 2012 innoQ Deutschland GmbH Map access & coupling (defn all-members [projects] (reduce conj #{} (flatten (map :team projects)))) #{{:id "2", :kind :fixed-price, :description "Development for Startup", :budget 100000, :team [:john, :chuck, :james, :bill]}} :team :team

Slide 43

Slide 43 text

© 2012 innoQ Deutschland GmbH [{:kind "fixed-price", :team ["john" "chuck" "james" "bill"], :budget 100000, :id "2", :description "Development for Startup"} {:kind "fixed-price", :team ["joe" "john"], :budget 3000, :id "3", :description "Clojure Training"} {:kind "time-material", :team ["joe" "chuck" "james"], :budget 25000, :id "1", :description "Consulting for BigCo"}] [{"kind":"fixed-price", "team":["john", "chuck", "james", "bill"], "budget":100000, "id":"2", "description":"Development for Startup"}, {"kind":"fixed-price", "team":["joe", "john"], "budget":3000, "id":"3", "description":"Clojure Training"}, {"kind":"time-material", "team":["joe", "chuck", "james"], "budget":25000, "id":"1", "description":"Consulting for BigCo"}] (json-str) (read-json)

Slide 44

Slide 44 text

© 2012 innoQ Deutschland GmbH (defn ...) (defmacro ...) (defmulti ...) (defmethod ...) (defn- ...) (def ^:private ...) (ns com.example.some-ns "Well-documented ns" (:use [com.example.n1 :only [xyz]]) (:require [com.example.ns2 :as n2]))

Slide 45

Slide 45 text

© 2012 innoQ Deutschland GmbH Implementation Interface Functions w/ simple data

Slide 46

Slide 46 text

© 2011 innoQ Deutschland GmbH Closures

Slide 47

Slide 47 text

© 2011 innoQ Deutschland GmbH (defn make-id [prefix id] (join "-" [prefix (Long/toString id 16)])) (prj-id) ;; "prj-1" (prj-id) ;; "prj-2" (prj-id) ;; "prj-3" (defn make-project [map] (assoc map :id (prj-id))) (defn id-generator ([prefix] (id-generator prefix 0)) ([prefix v] (let [cnt (atom v)] (fn [] (make-id prefix (swap! cnt inc)))))) (def prj-id (id-generator "prj"))

Slide 48

Slide 48 text

© 2012 innoQ Deutschland GmbH Meet Miss Grant http://www.informit.com/articles/article.aspx?p=1592379

Slide 49

Slide 49 text

© 2012 innoQ Deutschland GmbH (def fsm (make-fsm :idle :doorOpened {:idle [[unlock-door lock-panel] {:doorClosed :active}] :active [[] {:drawerOpened :waitingForLight :lightOn :waitingForDrawer}] :waitingForLight [[] {:lightOn :unlockedPanel}] :waitingForDrawer [[] {:drawerOpened :unlockedPanel}] :unlockedPanel [[unlock-panel lock-door] {:panelClosed :idle}]})) (defn unlock-door [] (println "Unlocking door")) (defn lock-door [] (println "Locking door")) (defn unlock-panel [] (println "Unlocking panel")) (defn lock-panel [] (println "Locking panel"))

Slide 50

Slide 50 text

© 2011 innoQ Deutschland GmbH (defn make-fsm "creates an fsm with initial state s0, a reset event, and a map of transitions. [state-transitions] must be a map of state->[[f1 f2 ...] {e0->s0, e1->s2, ...}]" [s0 reset-event state-transitions ] (let [s (atom s0)] (fn [evt] (if (= evt reset-event) (do (println "Reset event, returning to " s0) (swap! s (fn [_] s0))) (let [[actions transitions] (state-transitions @s)] (if-let [new-state (transitions evt)] (do (println "Event" evt "causes transition from" @s "to" new-state) (doseq [f actions] (f)) (swap! s (fn [_] new-state))) (println "Unexpected/unhandled event" evt "in state" @s)))))))

Slide 51

Slide 51 text

© 2011 innoQ Deutschland GmbH (dorun (map fsm [:doorClosed :lightOn :drawOpened :panelClosed])) (def fsm (make-fsm :idle :doorOpened {:idle [[unlock-door lock-panel] {:doorClosed :active}] :active [[] {:drawerOpened :waitingForLight :lightOn :waitingForDrawer}] :waitingForLight [[] {:lightOn :unlockedPanel}] :waitingForDrawer [[] {:drawerOpened :unlockedPanel}] :unlockedPanel [[unlock-panel lock-door] {:panelClosed :idle}]})) ;; Event :doorClosed causes transition from :idle to :active ;; Unlocking door ;; Locking panel ;; Event :lightOn causes transition from :active to :waitingForDrawer ;; Event :drawerOpened causes transition from :waitingForDrawer to :unlockedPanel ;; Event :panelClosed causes transition from :unlockedPanel to :idle ;; Unlocking panel ;; Locking door ;; Reset event, returning to :idle

Slide 52

Slide 52 text

© 2012 innoQ Deutschland GmbH Map Function Multimethod

Slide 53

Slide 53 text

© 2012 innoQ Deutschland GmbH Method problems “Global” state Coarse-grained re-use Simple-minded dispatch

Slide 54

Slide 54 text

© 2012 innoQ Deutschland GmbH Methods vs. Multimethods Methods Multimethods Dispatch Type customizable on # of args 1 arbitrary Hierarchy based on type inheritance customizable

Slide 55

Slide 55 text

© 2012 innoQ Deutschland GmbH Multimethods (def projects #{{:id "1", :kind :time-material, :description "Consulting for BigCo", :budget 25000, :team [:joe, :chuck, :james]} {:id "2", :kind :fixed-price, :description "Development for Startup", :budget 100000, :team [:john, :chuck, :james, :bill]} {:id "3", :kind :fixed-price, :description "Clojure Training", :budget 3000, :team [:joe, :john]}})

Slide 56

Slide 56 text

© 2012 innoQ Deutschland GmbH Multimethods (defmulti expected-revenue :kind) (defmethod expected-revenue :default [p] (:budget p)) (defmethod expected-revenue :fixed-price [p] (* 0.8 (:budget p))) (defn total-expected-revenue [projects] (reduce + (map expected-revenue projects)))

Slide 57

Slide 57 text

© 2012 innoQ Deutschland GmbH Multimethods (defn make-rectangle [[p1 p2 p3 p4 :as vertices]] (let [a (distance p1 p2) b (distance p2 p3)] (assert (= a (distance p3 p4))) (assert (= b (distance p4 p1))) {:kind :rectangle, :vertices vertices, :a a, :b b})) (defn make-circle [center r] {:kind :circle, :center center, :r r}) (defmulti area :kind) (defmethod area :rectangle [{:keys [a b]}] (* a b)) (defmethod area :circle [{:keys [r]}] (* PI (pow r 2)))

Slide 58

Slide 58 text

© 2012 innoQ Deutschland GmbH Multimethods (defmulti circumference :kind :default :polygon) (defmethod circumference :polygon [{:keys [vertices]}] (reduce + (map distance vertices (drop 1 (cycle vertices))))) (defmethod circumference :rectangle [{:keys [a b]}] (* 2 (+ a b)))

Slide 59

Slide 59 text

© 2012 innoQ Deutschland GmbH Multimethods (defmulti draw-shape (fn [shape canvas] [(:kind shape) (:type canvas)])) (defmethod draw-shape :default [shape canvas] (str "Drawing " (:kind shape) " on " (:type canvas))) (defmethod draw-shape [:circle :print-canvas] [shape canvas] "Printing a circle") (defmethod draw-shape [:rectangle :display-canvas] [shape canvas] "Showing a rectangle")

Slide 60

Slide 60 text

© 2011 innoQ Deutschland GmbH defrecord, de ype

Slide 61

Slide 61 text

© 2012 innoQ Deutschland GmbH Map Record Type Function Multimethod Protocol

Slide 62

Slide 62 text

© 2012 innoQ Deutschland GmbH defrecord Supports map access Flexible & extensible Convenience functions Better performance Platform integration Protocol support No structural sharing Code overhead

Slide 63

Slide 63 text

© 2012 innoQ Deutschland GmbH de ype No generic overhead Convenience functions Best performance Platform integration Protocol support No structural sharing No map access Static & xed Code overhead

Slide 64

Slide 64 text

© 2012 innoQ Deutschland GmbH Protocols (defprotocol Shape (area [shape]) (circumference [shape])) (defrecord Rectangle [vertices] Shape (area [shape] ...) (circumference [shape] ...)) (defrecord Circle [center r] Shape (area [shape] ...) (circumference [shape] ...))

Slide 65

Slide 65 text

© 2012 innoQ Deutschland GmbH Protocols (defprotocol ShapeStorage (read-from [storage]) (write-to [storage shape])) (extend-protocol ShapeStorage XmlStorage (read-from [storage] ...) (write-to [storage shape] ...) CouchDB (read-from [storage] ...) (write-to [storage shape] ...)) (extend-protocol ShapeStorage String (read-from [storage] ...) (write-to [storage shape] ...))

Slide 66

Slide 66 text

© 2012 innoQ Deutschland GmbH Protocols Performance Grouping Platform integration Limited dispatch (single arg, type-based)

Slide 67

Slide 67 text

© 2012 innoQ Deutschland GmbH Roadmap Recommendation 1 Namespaces, Functions, Persistent Data Structures 2 Multimethods 3 defrecord defprotocol 4 de ype

Slide 68

Slide 68 text

© 2012 innoQ Deutschland GmbH Summary

Slide 69

Slide 69 text

© 2012 innoQ Deutschland GmbH Functional programming is di erent (for a reason)

Slide 70

Slide 70 text

© 2012 innoQ Deutschland GmbH Question your own knowledge

Slide 71

Slide 71 text

© 2012 innoQ Deutschland GmbH Embrace data structures

Slide 72

Slide 72 text

© 2012 innoQ Deutschland GmbH Thanks! Q&A Stefan Tilkov [email protected] @stilkov innoQ Deutschland GmbH http://www.innoq.com Krischerstr. 100 40789 Monheim am Rhein Germany Phone: +49 2173 3366-0 innoQ Schweiz GmbH [email protected] Gewerbestr. 11 CH-6330 Cham Switzerland Phone: +41 41 743 0116