Clojure for OOP folks - Amazon S3s3-eu-west-1.amazonaws.com/presentations2013/30...A practical Lisp...
Transcript of Clojure for OOP folks - Amazon S3s3-eu-west-1.amazonaws.com/presentations2013/30...A practical Lisp...
Clojure for OOP folks Stefan Tilkov | @stilkov | innoQ
http://xkcd.com/297/
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
OOP Thinkingmodel domains with classes & interfaces
encapsulate data in objects
prefer speci!c over generic solutions
explicitly provide for generic access
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
27.04.10 14:17http://upload.wikimedia.org/wikipedia/en/1/1a/Clojure-glyph.svg
Page 1 of 1
Friday, May 17, 13
© 2011 innoQ Deutschland GmbH
Clojure 27.04.10 14:17http://upload.wikimedia.org/wikipedia/en/1/1a/Clojure-glyph.svg
Page 1 of 1
A practical Lisp variant for the JVM Functional programming
Dynamic Typing Full-featured macro system
Concurrent programming supportBi-directional Java interop
Immutable persistent data structures
Friday, May 17, 13
© 2012 innoQ Deutschland GmbHhttp://www.tbray.org/ongoing/When/200x/2008/09/25/-big/R0010774.jpg.html
Rich Hickey
Friday, May 17, 13
© 2011 innoQ Deutschland GmbH
Data structuresNumbers 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"}
Friday, May 17, 13
© 2011 innoQ Deutschland GmbH
Syntax
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
“You’ve just seen it” – Rich Hickey
Friday, May 17, 13
© 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)
Friday, May 17, 13
© 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])
Friday, May 17, 13
© 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")
Friday, May 17, 13
© 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
Friday, May 17, 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
Friday, May 17, 13
© 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
Friday, May 17, 13
© 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
Friday, May 17, 13
© 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."))))
Friday, May 17, 13
© 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
‣ …
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Syntax Idioms
Friday, May 17, 13
© 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
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Namespaces
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
... just like Java packages
Friday, May 17, 13
© 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 clashesReduce dependencies
Dynamic reloadingNamespace aliases
Convenient REPL usage
Flexible handling in sourcesProvide encapsulation
Friday, May 17, 13
© 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 clashesReduce dependencies
Dynamic reloadingNamespace aliasesConvenient REPL usage
Flexible handling in sourcesProvide encapsulation
Friday, May 17, 13
© 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]))
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Data
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Data structures vs. objectspublic 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])
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Data structures vs. objects(def p1 [3 4])
Immutable
Reusable
Compatible
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Data structures vs. objectsimport 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); }}
Friday, May 17, 13
© 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)))))
Friday, May 17, 13
© 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))
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Data structures vs. objects(defn circumference [vertices] (reduce + (map distance vertices (drop 1 (cycle vertices)))))
in!nite repetition
seq without !rstall
;((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
...
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
assocassoc-inbutlastconcatconjconscountcycledifferencedissocdistinctdistinct?drop-lastemptyempty?every?filterfirstflatten
group-byinterleaveinterposeintersectionintojoinlazy-catmapcatmergemerge-withnot-any?not-empty?not-every?nthpartitionpartition-allpartition-bypeekpop
popyprojectremovereplacerestrseqselectselect-keysshufflesomesplit-atsplit-withsubvectaketake-lasttake-nthtake-whileunionupdate-in
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
app
request
response
objects object
Friday, May 17, 13
© 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();}
Friday, May 17, 13
© 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;...
Friday, May 17, 13
© 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();...
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
app
request
response
data function
Friday, May 17, 13
© 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})
Friday, May 17, 13
© 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]}})
Friday, May 17, 13
© 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)
Friday, May 17, 13
© 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]}}
Friday, May 17, 13
© 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
Friday, May 17, 13
© 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)
Friday, May 17, 13
© 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]))
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Implementation
Interface
Functionsw/ simple data
Friday, May 17, 13
© 2011 innoQ Deutschland GmbH
Closures
Friday, May 17, 13
© 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"))
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Meet Miss Grant
http
://w
ww
.info
rmit.
com
/art
icle
s/ar
ticle
.asp
x?p=
1592
379
Friday, May 17, 13
© 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"))
Friday, May 17, 13
© 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)))))))
Friday, May 17, 13
© 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
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Map Function
Multimethod
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Method problems“Global” state
Coarse-grained re-use
Simple-minded dispatch
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Methods vs. Multimethods
Methods Multimethods
Dispatch Type customizable
on # of args 1 arbitrary
Hierarchybased on type
inheritancecustomizable
Friday, May 17, 13
© 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]}})
Friday, May 17, 13
© 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)))
Friday, May 17, 13
© 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)))
Friday, May 17, 13
© 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)))
Friday, May 17, 13
© 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")
Friday, May 17, 13
© 2011 innoQ Deutschland GmbH
defrecord, de#ype
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Map
Record
Type
Function
Multimethod
Protocol
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
defrecord
Supports map access
Flexible & extensible
Convenience functions
Better performance
Platform integration
Protocol support
No structural sharing
Code overhead
Friday, May 17, 13
© 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
Friday, May 17, 13
© 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] ...))
Friday, May 17, 13
© 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] ...))
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Protocols
Performance
Grouping
Platform integration
Limited dispatch(single arg, type-based)
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Roadmap Recommendation1
Namespaces, Functions,Persistent Data Structures
2 Multimethods
3defrecorddefprotocol
4 de!ype
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Summary
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Functional programmingis di"erent (for a reason)
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Question your own knowledge
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Embrace data structures
Friday, May 17, 13
© 2012 innoQ Deutschland GmbH
Thanks!
Q&A
Stefan [email protected]@stilkov
innoQ Deutschland GmbH
http://www.innoq.com
Krischerstr. 10040789 Monheim am RheinGermanyPhone: +49 2173 3366-0
innoQ Schweiz GmbH
Gewerbestr. 11CH-6330 ChamSwitzerlandPhone: +41 41 743 0116
Friday, May 17, 13