Clojure for OOP Folks: How to Design Clojure Programs

Clojure for OOP Folks: How to Design Clojure Programs

Your background is in OOP languages and you have become intrigued by Clojure. You know that things are different, and that continuing to design your programs in the same way you did in the past is likely going to cause problems. In this session, we will take a look at concepts that are likely to be familiar to you, such as encapsulation, type and class hierarchies, modularization, and domain modeling, and show what the analogies in idiomatic Clojure are. We will also take a look at some of the additional tools Clojure offers that go way beyond what you are used to — hopefully setting you off on your way to become a great Clojure programmer.

Afd6dc452bc20f8f06612d4792bb8be3?s=128

Stefan Tilkov

May 17, 2013
Tweet

Transcript

  1. Clojure for OOP folks Stefan Tilkov | @stilkov | innoQ

    http://xkcd.com/297/
  2. © 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
  3. © 2012 innoQ Deutschland GmbH http://upload.wikimedia.org/wikip

  4. © 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
  5. © 2012 innoQ Deutschland GmbH http://www.tbray.org/ongoing/When/200x/2008/09/25/-big/R0010774.jpg.html Rich Hickey

  6. © 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"}
  7. © 2011 innoQ Deutschland GmbH Syntax

  8. © 2012 innoQ Deutschland GmbH “You’ve just seen it” –

    Rich Hickey
  9. © 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)
  10. © 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])
  11. © 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")
  12. © 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
  13. © 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
  14. © 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
  15. © 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
  16. © 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 <pattern> <file...>") (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."))))
  17. © 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 ‣ …
  18. © 2012 innoQ Deutschland GmbH Syntax Idioms

  19. © 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
  20. © 2012 innoQ Deutschland GmbH Namespaces

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

  22. © 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
  23. © 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
  24. © 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]))
  25. © 2012 innoQ Deutschland GmbH Data

  26. © 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])
  27. © 2012 innoQ Deutschland GmbH Data structures vs. objects (def

    p1 [3 4]) Immutable Reusable Compatible
  28. © 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); } }
  29. © 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)))))
  30. © 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))
  31. © 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 ...
  32. © 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
  33. © 2012 innoQ Deutschland GmbH app request response objects object

  34. © 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(); }
  35. © 2012 innoQ Deutschland GmbH public interface HttpServletRequest extends ServletRequest

    { public String getAuthType(); public Cookie[] getCookies(); public Enumeration<String> getHeaders(String name); public Enumeration<String> 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; ...
  36. © 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(); ...
  37. © 2012 innoQ Deutschland GmbH app request response data function

  38. © 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})
  39. © 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]}})
  40. © 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)
  41. © 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]}}
  42. © 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
  43. © 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)
  44. © 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]))
  45. © 2012 innoQ Deutschland GmbH Implementation Interface Functions w/ simple

    data
  46. © 2011 innoQ Deutschland GmbH Closures

  47. © 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"))
  48. © 2012 innoQ Deutschland GmbH Meet Miss Grant http://www.informit.com/articles/article.aspx?p=1592379

  49. © 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"))
  50. © 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)))))))
  51. © 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
  52. © 2012 innoQ Deutschland GmbH Map Function Multimethod

  53. © 2012 innoQ Deutschland GmbH Method problems “Global” state Coarse-grained

    re-use Simple-minded dispatch
  54. © 2012 innoQ Deutschland GmbH Methods vs. Multimethods Methods Multimethods

    Dispatch Type customizable on # of args 1 arbitrary Hierarchy based on type inheritance customizable
  55. © 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]}})
  56. © 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)))
  57. © 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)))
  58. © 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)))
  59. © 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")
  60. © 2011 innoQ Deutschland GmbH defrecord, de ype

  61. © 2012 innoQ Deutschland GmbH Map Record Type Function Multimethod

    Protocol
  62. © 2012 innoQ Deutschland GmbH defrecord Supports map access Flexible

    & extensible Convenience functions Better performance Platform integration Protocol support No structural sharing Code overhead
  63. © 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
  64. © 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] ...))
  65. © 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] ...))
  66. © 2012 innoQ Deutschland GmbH Protocols Performance Grouping Platform integration

    Limited dispatch (single arg, type-based)
  67. © 2012 innoQ Deutschland GmbH Roadmap Recommendation 1 Namespaces, Functions,

    Persistent Data Structures 2 Multimethods 3 defrecord defprotocol 4 de ype
  68. © 2012 innoQ Deutschland GmbH Summary

  69. © 2012 innoQ Deutschland GmbH Functional programming is di erent

    (for a reason)
  70. © 2012 innoQ Deutschland GmbH Question your own knowledge

  71. © 2012 innoQ Deutschland GmbH Embrace data structures

  72. © 2012 innoQ Deutschland GmbH Thanks! Q&A Stefan Tilkov stefan.tilkov@innoq.com

    @stilkov innoQ Deutschland GmbH http://www.innoq.com Krischerstr. 100 40789 Monheim am Rhein Germany Phone: +49 2173 3366-0 innoQ Schweiz GmbH info@innoq.com Gewerbestr. 11 CH-6330 Cham Switzerland Phone: +41 41 743 0116