Slide 1

Slide 1 text

Clojure in Practice Philipp Schirmacher & Silvia Schreier innoQ

Slide 2

Slide 2 text

2 / 71

Slide 3

Slide 3 text

3 / 71 ● A practical Lisp for the JVM ● Functional programming ● Dynamic typing ● Full-featured macro system ● Concurrent programming support ● Bi-directional Java interop ● Immutable persistent data structures ● Software transactional memory

Slide 4

Slide 4 text

4 / 71 Lisp??

Slide 5

Slide 5 text

5 / 71 Lots of irritating silly parenthesis

Slide 6

Slide 6 text

6 / 71

Slide 7

Slide 7 text

7 / 71 MyClass.java Bytecode javac

Slide 8

Slide 8 text

8 / 71 MyClass .java Bytecode javac my_name space.clj Clojure Compiler

Slide 9

Slide 9 text

9 / 71 Clojure Crash Course

Slide 10

Slide 10 text

10 / 71 Generic Data Types

Slide 11

Slide 11 text

11 / 71 {:name "Clojure" :features [:functional :jvm :parens] :creator "Rich Hickey" :stable-version {:number "1.6.0" :release "2014/05/23"}}

Slide 12

Slide 12 text

12 / 71 Functions

Slide 13

Slide 13 text

13 / 71 (+ 1 2) > 3 (:city {:name "innoQ" :city "Monheim"}) > "Monheim" (map inc [1 2 3]) > (2 3 4)

Slide 14

Slide 14 text

14 / 71 (map inc [1 2 3]) > (2 3 4) (filter odd? [1 2 3 4 5]) > (1 3 5) (reduce + 0 [1 2 3]) > 6

Slide 15

Slide 15 text

15 / 71 (fn [x y] (+ x y)) > # ((fn [x y] (+ x y)) 1 2) > 3 (def add (fn [x y] (+ x y))) (defn add [x y] (+ x y)) (add 1 2) > 3

Slide 16

Slide 16 text

16 / 71 (defn statistics [numbers] {:sum (reduce + 0 numbers) :count (count numbers) :average (/ (reduce + 0 numbers) (count numbers))})

Slide 17

Slide 17 text

17 / 71 (defn statistics [numbers] (let [sum (reduce + 0 numbers) cnt (count numbers)] {:sum sum :count cnt :average (/ sum cnt)})) (statistics [1 2 3 4 5]) > {:sum 15 :count 5 :average 3}

Slide 18

Slide 18 text

18 / 71 (defn statistics "computes sum, count and average for a collection of numbers" [numbers] (let [sum (reduce + 0 numbers) cnt (count numbers)] {:sum sum :count cnt :average (/ sum cnt)})) (statistics [1 2 3 4 5]) > {:sum 15 :count 5 :average 3}

Slide 19

Slide 19 text

19 / 71 (defn statistics "computes sum, count and average for a collection of numbers" [numbers] (let [sum (reduce + 0 numbers) cnt (count numbers)] {:sum sum :count cnt :average (/ sum cnt)})) (statistics [1 2 3 4 5]) > {:sum 15 :count 5 :average 3} (ns my.company.numerics)

Slide 20

Slide 20 text

20 / 71 Ok. But how to build an app!?

Slide 21

Slide 21 text

21 / 71 Demo https://github.com/innoq/imagizer

Slide 22

Slide 22 text

22 / 71 ● How to set up a project? ● How to speak HTTP? ● How to produce HTML? ● How to load & consume HTML? ● How to work with JSON? ● How to access a database? ● How to process images?

Slide 23

Slide 23 text

23 / 71 How to set up a project?

Slide 24

Slide 24 text

24 / 71 Leiningen ● Alternative to Maven ● Describe Clojure project with generic data structures ● Maven repository compatibility

Slide 25

Slide 25 text

25 / 71 Leiningen (defproject imagizer "0.1.0-SNAPSHOT" :description "yet another clojure demo app" :dependencies [[org.clojure/clojure "1.6.0"] [ring "1.3.1"] [compojure "1.1.9"] [hiccup "1.0.5"] [yesql "0.4.0"] [ring/ring-json "0.3.1"] [com.h2database/h2 "1.4.181"] [org.clojure/java.jdbc "0.3.5"] [ragtime "0.3.7"]] :plugins [[lein-ring "0.8.12"] [lein-cljsbuild "1.0.3"] [ragtime/ragtime.lein "0.3.7"]] :ring {:handler imagizer.core/webapp})

Slide 26

Slide 26 text

26 / 71 Leiningen $ lein new app imagizer $ cd imagizer $ tree . ├── LICENSE ├── README.md ├── project.clj ├── resources │ └── public │ ├── img │ │ └── icon.svg │ ├── js │ │ └── imagizer.js │ └── stylesheets │ └── imagizer.css ├── src │ └── imagizer │ └── core.clj └── test └── imagizer └── core_test.clj

Slide 27

Slide 27 text

27 / 71 Leiningen $ lein help Leiningen is a tool for working with Clojure projects. Several tasks are available: check Check syntax and warn on reflection. classpath Print the classpath of the current project. clean Remove all files from project's target-path. deps Download all dependencies. jar Package up all the project's files into a jar file. ring Manage a Ring-based application. ... $ lein ring server 2014-10-22 10:22:15.237:INFO:oejs.Server:jetty-7.6.13.v20130916 2014-10-22 10:22:15.468:INFO:oejs.AbstractConnector:Started [email protected]:3000 Started server on port 3000

Slide 28

Slide 28 text

28 / 71 Why Leiningen? ● Easy to extend via plugins ● Plugins can process project definition easily because it's just data

Slide 29

Slide 29 text

29 / 71 How to speak HTTP?

Slide 30

Slide 30 text

30 / 71 Ring & Compojure ● Alternative to Spring MVC ● Request & response are data [1] ● Webapp is a function which takes a request and returns a response [1] https://github.com/mmcgrana/ring/blob/master/SPEC

Slide 31

Slide 31 text

31 / 71 Ring & Compojure (def example-request {:uri "/index.html" :request-method :get :headers {"accept" "text/html"}}) (def example-response {:status 200 :headers {"Content-Type" "text/html"} :body "..."}) (defn hello-world-webapp [req] {:status 200 :body "hello, world!"})

Slide 32

Slide 32 text

32 / 71 Ring & Compojure (defroutes webapp (GET "/" [] homepage) (GET "/images" [url] (images-page url)) (GET "/image" [src] (image-page src)) (POST "/image" [src op] (convert-and-store-image src op)) (GET "/preview" [src op] (image-preview src op)) (GET "/result/:uuid" [uuid] (result-page uuid)) (POST "/result/:uuid/tags" [uuid tag] (add-tag uuid tag)) (GET "/tags" [] (tags-json)) (GET "/tags/:tag" [tag] (tag-page tag)) (GET "/static/:uuid" [uuid] (filtered-file uuid)) (route/resources "/") (route/not-found "oops - not found"))

Slide 33

Slide 33 text

33 / 71 Ring & Compojure (webapp {:request-method :get :uri "/"}) > {:status 200 :headers {"Content-Type" "text/html"} :body "..."} (webapp {:request-method :get :uri "/unknown"}) > {:status 404 :headers {"Content-Type" "text/plain"} :body "oops - not found"}

Slide 34

Slide 34 text

34 / 71 Ring & Compojure (defproject imagizer "0.1.0-SNAPSHOT" :description "yet another clojure demo app" :dependencies [[org.clojure/clojure "1.6.0"] [ring "1.3.1"] [compojure "1.1.9"] [hiccup "1.0.5"] [yesql "0.4.0"] [ring/ring-json "0.3.1"] [com.h2database/h2 "1.4.181"] [org.clojure/java.jdbc "0.3.5"] [ragtime "0.3.7"]] :plugins [[lein-ring "0.8.12"] [lein-cljsbuild "1.0.3"] [ragtime/ragtime.lein "0.3.7"]] :ring {:handler imagizer.core/webapp})

Slide 35

Slide 35 text

35 / 71 Why Ring & Compojure? ● Simplicity ● Easy to test ● Common request/response format used across libraries

Slide 36

Slide 36 text

36 / 71 How to produce HTML?

Slide 37

Slide 37 text

37 / 71 Hiccup ● Alternative to JSPs ● Represent HTML with Clojure data structures ● Transform data structure to HTML string

Slide 38

Slide 38 text

38 / 71 Hiccup bar [:element] [:element {:attribute "foo"}] [:element {:attribute "foo"} [:nested "bar"]]

Slide 39

Slide 39 text

39 / 71 Hiccup (def homepage [:html [:head [:title "my page"]] [:body [:h1 "welcome"] [:p "just some text"]]]) (html homepage) > "my page ..."

Slide 40

Slide 40 text

40 / 71 Hiccup (link-to "http://www.innoq.com" "click here") > [:a {:href "http://www.innoq.com"} "click here"] (form-to [:post "/login"] (text-field "Username") (password-field "Password") (submit-button "Login")) > [:form {:action "POST" ...} [:input ...] ...]

Slide 41

Slide 41 text

41 / 71 Hiccup (defn tag-item [tag] [:li [:a {:href (str "/tags/" tag)} tag]]) (defn tag-list [tags] [:ul.tags (map tag-item tags)]) (tag-list ["foo" "bar" "baz"]) > [:ul.tags ([:li [:a {:href "/tags/foo"} "foo"]] [:li ...] [:li ...])]

Slide 42

Slide 42 text

42 / 71 Why Hiccup? ● Simple, yet powerful ● HTML is just data, so no special language required to manipulate it

Slide 43

Slide 43 text

43 / 71 What do we have so far? ● Project set up (Leiningen) ● HTTP basics (Ring + Compojure) ● HTML (Hiccup)

Slide 44

Slide 44 text

44 / 71 What do we have so far?

Slide 45

Slide 45 text

45 / 71 What we want!

Slide 46

Slide 46 text

46 / 71 How to load & consume HTML?

Slide 47

Slide 47 text

47 / 71 clj-http & Hickory ● Alternatives to Jersey HTTP Client and jsoup ● Built on Apache HTTP Client ● Ring-compatible request/response format ● Hiccup-compatible HTML representation

Slide 48

Slide 48 text

48 / 71 clj-http & Hickory (http/get "http://www.google.de") > {:cookies {} :body "" :trace-redirects ["http://www.google.de"], :request-time 151, :status 200, :headers {"Server" "gws" "Content-Type" "text/html; charset=ISO-8859-1" "X-Frame-Options" "SAMEORIGIN" "Connection" "close" "Alternate-Protocol" "80:quic,p=0.01" "Expires" "-1" "Date" "Wed, 22 Oct 2014 13:59:12 GMT" "X-XSS-Protection" "1; mode=block" "Cache-Control" "private, max-age=0"}}

Slide 49

Slide 49 text

49 / 71 clj-http & Hickory (http/get "http://picjumbo.com/wp-content/uploads/HNCK0619-1300x866.jpg" {:as :byte-array}) > {:status 200 :headers {...} :body #} (http/get "http://some.rest.api" {:headers {"Accept" "application/json"}}) > {:status 200 :headers {"Content-Type" "application/json"} :body "{\"json\" : [\"stuff\"]}"}

Slide 50

Slide 50 text

50 / 71 clj-http & Hickory > [:html {} [:head {}] [:body {} "foo"]] (hickory/as-hiccup (hickory/parse "foo")) (-> "foo" hickory/parse hickory/as-hiccup) > [:html {} [:head {}] [:body {} "foo"]]

Slide 51

Slide 51 text

51 / 71 clj-http & Hickory (-> "foo" hickory/parse hickory/as-hiccup) > [:html {} [:head {}] [:body {} "foo"]] (defn load-html [url] (-> url http/get :body hickory/parse hickory/as-hiccup))

Slide 52

Slide 52 text

52 / 71 clj-http & Hickory (load-html "http://picjumbo.com/category/animals/") > [:html {:xmlns "http://www.w3.org/1999/xhtml"} [:head {}] [:body ...lots of stuff... [:img {:src ""}] ...more stuff...]]

Slide 53

Slide 53 text

53 / 71 clj-http & Hickory (doc tree-seq) ------------------------- clojure.core/tree-seq ([branch? children root]) Returns a lazy sequence of the nodes in a tree, via a depth-first walk. branch? must be a fn of one arg that returns true if passed a node that can have children (but may not). children must be a fn of one arg that returns a sequence of the children. Will only be called on nodes for which branch? returns true. Root is the root node of the tree. (defn all-elements [hiccup-html] (let [might-have-children? vector? children (fn [node] (drop 2 node))] (tree-seq might-have-children? children hiccup-html))) (defn find-images [url] (let [html (load-html url)] (filter (fn [elem] (and (vector? elem) (= :img (first elem)))) (all-elements html))))

Slide 54

Slide 54 text

54 / 71 Why clj-http & Hickory? ● Well-known request/response format ● Well-known HTML representation ● Once again: easy to process because it's just data

Slide 55

Slide 55 text

55 / 71 How to access a database?

Slide 56

Slide 56 text

56 / 71 yesql & ragtime ● yesql: alternative to Spring JdbcTemplate ● Don't build a DSL around a DSL ● Parses SQL queries into Clojure functions ● ragtime: migrating structured data ● Common interface for migrations like Ring for HTTP

Slide 57

Slide 57 text

57 / 71 yesql & ragtime -- name: all-img-tags -- finds all existing image tags SELECT DISTINCT Tag FROM Image_Tag; src/db/all_image_tags.sql (defqueries "db/all_image_tags.sql") (all-img-tags db-spec) > [{:tag "animal"} {:tag "blur"} {:tag "funny"}]

Slide 58

Slide 58 text

58 / 71 yesql & ragtime -- name: add-tag! INSERT INTO Image_Tag(Image, Tag) VALUES (:file, :tag); src/db/add_tag.sql (defqueries "db/add_tag.sql") (add-tag! db-spec uuid tag)

Slide 59

Slide 59 text

59 / 71 yesql & ragtime CREATE TABLE Image_Tag (Tag varchar(200), Image varchar(40)); migrations/20141010-add-image-tag.up.sql DROP TABLE Image_Tag; migrations/20141010-add-image-tag.down.sql

Slide 60

Slide 60 text

60 / 71 yesql & ragtime > lein ragtime migrate :plugins [[lein-ring "0.8.12"] [ragtime/ragtime.lein "0.3.7"]] :ragtime {:migrations ragtime.sql.files/migrations :database "jdbc:h2:./db/data"} project.clj

Slide 61

Slide 61 text

61 / 71 Why yesql & ragtime? ● No DSL for a DSL ● Simple ● Easy data processing

Slide 62

Slide 62 text

62 / 71 How to process images?

Slide 63

Slide 63 text

63 / 71 Java interop ● Using the rich JVM ecosystem ● Im4java: Java interface for ImageMagick ● It is easy to create objects, access attributes and call methods ● Bean/Java-Object ↔ Map

Slide 64

Slide 64 text

64 / 71 Java interop (def uuid (java.util.UUID/randomUUID)) uuid > #uuid "8ce47fde-8c01-4396-8dea-4ec0d0ef88d5" (.length (.toString uuid)) > 36 (-> uuid (.toString) (.length)) > 36 (-> uuid (.toString) (count)) > 36

Slide 65

Slide 65 text

65 / 71 Java interop (ns imagizer.core (:import [org.im4java.core Info])) (new Info "source.jpg") (Info. "source.jpg") (let [img-info (Info. "source.jpg")] (.getImageWidth img-info))

Slide 66

Slide 66 text

66 / 71 Why Java interop? ● Good built-in support ● Utilizing whole ecosystem ● But be careful with mutable state ● Use conversion to map instead

Slide 67

Slide 67 text

67 / 71 Summary

Slide 68

Slide 68 text

68 / 71 Java Clojure Configuration Maven Leiningen HTTP server SpringMVC Ring + Compojure HTML templating JSP Hiccup HTTP client Jersey HTTP Client clj-http HTML parsing Jsoup Hickory JSON handling Jackson data.json Database access Spring JdbcTemplate yesql Migrations Liquibase ragtime JVM Interop - built-in There is more to discover!

Slide 69

Slide 69 text

69 / 71 :-) ● Simple basic concepts ● Easy to use ● Helpful community ● Mature eco system

Slide 70

Slide 70 text

70 / 71 Give it a try! https://github.com/innoq/imagizer

Slide 71

Slide 71 text

Silvia Schreier [email protected] @aivlis_s Thank you! Questions? http://www.innoq.com Philipp Schirmacher [email protected] @pschirmacher