Clojure Deep
Dive



Howard M. Lewis Ship
TWD Consulting
hlship@gmail.com

                       1   © 2009 Howard Lewis Ship
Agenda
• Core Language

• Standard Tools

• Clojure Compilation

• Clojure Pitfalls




                        2   © 2009 Howard Lewis Ship
Core Language

           3    © 2009 Howard Lewis Ship
What's A Form?
          Constants:      Keywords:        Symbols:

          42              :foo             map
          "hello"         :tag             strutils2/take



                        Data Structures:

                        […]
                        {…}
                        #{ … }


  Callables:
   Special Forms:      Function Call:      Macros Expansion:

   (if …)              (map …)             (and …)
   (var …)                                 (or …)
   (do …)                                  (ns …)



                                 4                             © 2009 Howard Lewis Ship
defn: Define a function
                     Symbol (in current namespace)
                                                              Optional doc string

            (defn to-seq
              "Converts the object to a sequence (a vector)
            unless it is already sequential."
              [obj]
              (if (sequential? obj) obj [obj]))



List of parameters




    user=> (doc to-seq)
    -------------------------
    user/to-seq
    ([obj])
      Converts the object to a sequence (a vector)
    unless it is already sequential.
    nil
    user=>


                                             5                                      © 2009 Howard Lewis Ship
defn: Multiple Arities
One doc string
Series of argument lists / forms

                           (defn not=
                             "Same as (not (= obj1 obj2))"
                             ([x] false)
                             ([x y] (not (= x y)))
                             ([x y & more]
                              (not (apply = x y more))))

              & symbol ➠ provide
              remaining values as a
              seq


user=> (doc not=)
-------------------------
clojure.core/not=
([x] [x y] [x y & more])
  Same as (not (= obj1 obj2))
nil
user=>


                                           6                 © 2009 Howard Lewis Ship
let: local symbols
        (let [center (calc-center-for-frame frame)
             x (get-x center)
             y (get-y center)]
          …)




•Even number: symbol1 form1 symbol2 form2 …

•Not variables: symbols are read-only

•Can re-define symbols




                                     7               © 2009 Howard Lewis Ship
do: evaluate for side-effects

    (if value-found
      (do
        (println "Found the value.")
        value-found))



        Last form is the result




    ; Broken

    (if value-found
      (println "Found the value.")               If true, side effect & return nil
      value-found)




     If false, return value (false or nil)



                                             8                                 © 2009 Howard Lewis Ship
Branching: if, when
                       (if test then-form)
                       (if test then-form else-form)




• Special form: not a function
• Evaluates test, then evaluates & returns either then-form or
  else-form
  • false and nil ➠ logical false
  • anything else ➠ logical true
        (when test then-forms)             (if-not test then-form)
                                           (if-not test then-form else-form)


           Any number of forms in an
           implicit do                          Like (if (not test) then else)


        (when-not test then-forms)
                                       9                                         © 2009 Howard Lewis Ship
if-let, when-let

(if-let [symbol test] then-form)
(if-let [symbol test] then-form else-form)



             Symbol can be referenced


 (when-let [symbol test] then-forms)



              Any number of forms in an
              implicit do




                                          10   © 2009 Howard Lewis Ship
or, and
                                        (or form…)
                                        (and form…)

   • or: first value from list that evaluates to true
      • Will return value of last expression (false or nil)
      • (or) returns nil
   • and: first logical false (nil or false)
      • Returns value of last expression (if prior are logical true)
      • (and) return true
      • Can be used as a "guard"                      Only invoke symbol and find-ns
(defn dispatch-named-function-to-pipeline             if ns-name is non-nil
  [env pipeline]
  (let [split-path (-> env :cascade :split-path)
        [_ ns-name fn-name] split-path
       fn-namespace (and ns-name (find-ns (symbol ns-name)))
       named-function (and fn-namespace fn-name (ns-resolve fn-namespace (symbol fn-name)))
       new-env (assoc-in env [:cascade :extra-path] (drop 3 split-path))]
    (call-pipeline pipeline new-env named-function)))

                                             11                                © 2009 Howard Lewis Ship
Destructuring




                                                                           Bu

                                                                            N wo
                                                                             sy
                                                                              o rk
• Automatically extract values from data structures




                                                                                            !
• Binding forms:

   • Map to extract keys

   • Vector to extract indexed values

• Function parameters
                                                 One un-named parameter
• let, if-let, when-let, etc.                    destructured to two symbols


                 user=> (defn natural-name
                          [{fname :first-name lname :last-name}]
                          (str lname ", " fname))
                 #'user/natural-name
                 user=> (natural-name (persons 1))
                 "Simon, Scott"
                 user=>

                                          12                               © 2009 Howard Lewis Ship
Destructuring
• Binding Forms:

  • symbol                                      (let [x (get-x center)] … )




  • vector of binding forms                         (let [[x y] center] … )




  • map of binding form to map key
                       (let [{fname :first-name lname :last-name} person] …)




                                 13                                © 2009 Howard Lewis Ship
Vector Destructuring
                 center must be sequential


(let [[x y] center] …)




      May be nil if not enough values in center


(let [[first-born second-born & other-children] children] …)



                              & symbol gets the remaining values

(let [[first-born second-born & other-children :as children] (find-children parent)] …)



            Nested binding forms!                 :as symbol gets the full list

user=> (let [[[x1 y1][x2 y2]] [[1 2] [3 4]]]
  [x1 y1 x2 y2])
[1 2 3 4]
user=>
                                             14                                   © 2009 Howard Lewis Ship
Map Destructuring
(defn natural-name [{fname :first-name lname :last-name}] (str lname ", " fname))



(defn natural-name [{:keys [first-name last-name]}] (str last-name ", " first-name))



                    :keys maps a keyword key for each symbol
                    :or sets defaults for missing keys

user=> (defn natural-name [{:keys [first-name last-name]
                            :or {first-name "(none)" last-name "(none)"}}]
         (str last-name ", " first-name))
#'user/natural-name
user=> (natural-name {:first-name "Gromit"})
"(none), Gromit"
user=> (natural-name {:first-name "Gromit" :last-name nil})
", Gromit"
user=>




          :as symbol to get the original map
          :strs ➠ like :keys, but maps to string keys, not keyword keys
          :syms ➠ like :keys, but maps to symbol keys, not keyword keys

                                         15                                  © 2009 Howard Lewis Ship
ns: define namespaces
(ns example.utils)
                                  cascade/dispatcher.clj

                                  (ns cascade.dispatcher
                                    (:import (javax.servlet ServletResponse))
      File: example/utils.clj       (:use (cascade config dom logging path-map pipeline)
                                          (cascade.internal utils)))


(ns namespace
  (:import …)   Optional directives
  (:use …))




                      user=> (defn natural-name
                               [{fname :first-name lname :last-name}]
                               (str lname ", " fname))
                      #'user/natural-name
                      user=> (natural-name (persons 1))
                      "Simon, Scott"
                      user=>

user/natural-name
  Namespace: user
  Symbol: natural-name
                                                 16                           © 2009 Howard Lewis Ship
:import Java Classes
                      Package name, then any number of classes/interfaces

(ns cascade.dispatcher
  (:import (javax.servlet ServletResponse))

  …)


            Repeat for other packages

            Can also list fully qualify class names




                                             17                             © 2009 Howard Lewis Ship
:use Import Namespaces
(:use clojure.contrib.pprint)


         Symbols defined by pprint are available, i.e.
         (pprint [1 2 3])

(:use (clojure.contrib pprint monads))


         Add multiple namespaces under the root
         namespace

(:use (clojure.contrib (str-utils2 :only [join map-str]))



         Uses just join and map-str from str-utils2

(:use (clojure.contrib (str-utils2 :exclude [take replace drop]))

         Exclude just some of the symbols
(:use (clojure.contrib (str-utils2 :rename {take strtake}))

         Use str-utils2, mapping take as strtake

                                            18                      © 2009 Howard Lewis Ship
Other ns directives
• :require ➠ load a namespace but don't import it

• :gen-class ➠ create a Java class from functions in the
  namespace

• :refer-clojure ➠ import selected clojure.core symbols

• :load ➠ load Clojure scripts (not namespaces)




                                19                         © 2009 Howard Lewis Ship
Standard
Tools




           20   © 2009 Howard Lewis Ship
Anonymous Functions
  #() ➠ implicit anonymous function

  (filter #(not (.startsWith % ".")) names)




           % is replaced by the function's parameter

        %n is parameter #n


 (map
   #(assoc %1 :sort-index %2)
   (sort-by :age persons)
   (iterate inc 0))

                                 inline anonymous function


                             (map
                        21
                               (fn [person-map sort-index]
                                  (assoc person-map :sort-index sort-index))
                               (sort-by :age persons)
                               (iterate inc 0))

                                                                               © 2009 Howard Lewis Ship
What are seqs?
                Seqable
              seq() : ISeq




          IPersistentCollection                                      Sequential
              count() : int
   cons(Object): IPersistentCollection
     empty() : IPersistentCollection
        equiv(Object) : boolean




              PersistentSet                       ISeq
                                             first() : Object
                                              next(): ISeq
                                              more(): ISeq
                                           cons(Object): ISeq        Returned from map, for,
                                                                     filter, remove, etc.


                          PersistentList     PersistentVector   LazySeq



                                                         22                               © 2009 Howard Lewis Ship
assoc-in / update-in
user=> (def person { :first-name "Howard" :last-name "Lewis Ship"
  :address { :street "123 NW 12th Ave. #541" :city "Portland" :state "OR" :zip 97309 }})
#'user/person
user=> (assoc-in person [:address :street] "2647 SE 97th Ave.")
{:first-name "Howard", :last-name "Lewis Ship",
 :address {:street "2647 SE 97th Ave.", :city "Portland", :state "OR", :zip 97309}}
user=> (update-in person [:address :zip] + 5)
{:first-name "Howard", :last-name "Lewis Ship",
 :address {:street "123 NW 12th Ave. #541", :city "Portland", :state "OR", :zip 97314}}
user=>

                            :first-name "Howard"
                            :last-name "Lewis Ship"
                            :address :street "123 NW 12th Ave."
                                        :city "Portland"
                                        :state "OR"
                                        :zip 97309


                      (update-in person [:address :zip] + 5)

                                       (+ 97309 5)

                            :first-name "Howard"
                            :last-name "Lewis Ship"
                            :address :street "123 NW 12th Ave."
                                        :city "Portland"
                                        :state "OR"
                                        :zip 97314

                                              23                            © 2009 Howard Lewis Ship
map




                                                                            La
                                                                                zy
                                       f

             :age acts as a function


 user=> (map :age persons)
 (42 44 29)
 user=> (map #(apply str (reverse (% :first-name))) persons)
 ("drawoH" "ttocS" "ylloM")
 user=> (map identity (persons 0))
 ([:first-name "Howard"]            iterate a map ➠ key/value   pairs
  [:last-name "Lewis Ship"]
  [:age 42])
 user=> (seq (persons 0))
 ([:first-name "Howard"]
  [:last-name "Lewis Ship"]
  [:age 42])
 user=>



                                           24                           © 2009 Howard Lewis Ship
map




                                                                            La
                                                                                zy
                                  f


                     N seqs ➠ N parameters


 user=> (map #(assoc %1 :sort-index %2)
             (sort-by :age persons)
             (iterate inc 0))
 ({:sort-index 0, :first-name "Molly", :last-name "Newman", :age 29}
  {:sort-index 1, :first-name "Howard", :last-name "Lewis Ship", :age 42}
  {:sort-index 2, :first-name "Scott", :last-name "Simon", :age 44})
 user=>




                                      25                                © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))




                                       26             © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))

                      (reduce + '(42 44 29 38))




                                       27             © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))

                      (reduce + '(42 44 29 38))

                      (+ (+ (+ 42 44) 29) 38)




                                       28             © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))

                      (reduce + '(42 44 29 38))

                      (+ (+ (+ 42 44) 29) 38)

                      (+ (+ 86 29) 38)




                                       29             © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))

                      (reduce + '(42 44 29 38))

                      (+ (+ (+ 42 44) 29) 38)

                      (+ (+ 86 29) 38)

                      (+ 115 38)


                                       30             © 2009 Howard Lewis Ship
reduce

                                            f
user=> (map :age persons)
(42 44 29 38)
user=> (reduce + (map :age persons))
153



                      (reduce + (map :age persons))

                      (reduce + '(42 44 29 38))

                      (+ (+ (+ 42 44) 29) 38)

                      (+ (+ 86 29) 38)

                      (+ 115 38)

                      153
                                       31             © 2009 Howard Lewis Ship
reduce


                                                         f
                      user=> (def input-string "Clojure is a fascinating language with unique
                      capabilities and total integration with Java.")
                      #'user/input-string
                      user=> (seq input-string)
                      (C l o j u r e space i s space a space f a s c i n a
                      t i n g space l a n g u a g e space w i t h space u
                      n i q u e space c a p a b i l i t i e s space a n d
                      space t o t a l space i n t e g r a t i o n space w
                      i t h space J a v a .)
                      user=> (reduce
                               (fn [m k] (update-in m [k] #(if (nil? %) 1 (inc %))))
Optional initial   value       {}
                               (seq input-string))
                      {space 12, a 12, b 1, C 1, c 2, d 1, e 5, f 1, g 4, h 2, i 11,
                       J 1, j 1, l 4, . 1, n 7, o 3, p 1, q 1, r 2, s 3, t 8, u 4,
                       v 1, w 2}
                      user=>


                                                   32                                © 2009 Howard Lewis Ship
filter / remove




                                                                La
                                                                    zy
                                              f
                                                  ?
user=> (remove #(< (% :age) 30) persons)
({:first-name "Howard", :last-name "Lewis Ship", :age 42}
 {:first-name "Scott", :last-name "Simon", :age 44})
user=> (filter #(< (% :age) 30) persons)
({:first-name "Molly", :last-name "Newman", :age 29})
user=>




                                         33                 © 2009 Howard Lewis Ship
apply

                                             f

  user=> (map :age persons)
  (42 44 29)
  user=> (apply + (map :age persons))
  115
  user=>


                  (apply + (map :age persons))

                  (apply + '(42 44 29))

                  (+ 42 44 29)

                  115



                                        34       © 2009 Howard Lewis Ship
Clojure Compilation

           35         © 2009 Howard Lewis Ship
Function Calls


   (remove nil? coll)



                                                       IFn

                                                invoke() : Object
                                             invoke(Object) : Object
       Namespace                          invoke(Object, Object) : Object
                                                        …
                                              applyTo(ISeq) : Object




       Var
       get()            clojure.core$remove__4782    PersistentMap          Keyword




                              36                                               © 2009 Howard Lewis Ship
Compiled to Bytecode
                                (defn first-non-nil
                                  "Returns the first non-nil value from the collection."
                                  [coll]
                                  (first (remove nil? coll)))



package com.howardlewisship.cascade.internal;

import clojure.lang.*;

public class utils$first_non_nil__24 extends AFunction
{

    public Object invoke(Object coll)
      throws Exception
    {
      return ((IFn)const__0.get()).invoke(((IFn)const__1.get()).
             invoke(const__2.get(), coll = null));
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "first");
    public static final Var const__1 = (Var)RT.var("clojure.core", "remove");
    public static final Var const__2 = (Var)RT.var("clojure.core", "nil?");


    public utils$first_non_nil__24()
    {
    }
}
                                              37                                © 2009 Howard Lewis Ship
Compiled to Bytecode
                                (defn first-non-nil
                                  "Returns the first non-nil value from the collection."
                                  [coll]
                                  (first (remove nil? coll)))



package com.howardlewisship.cascade.internal;

import clojure.lang.*;

public class utils$first_non_nil__24 extends AFunction
{

    public Object invoke(Object coll)
      throws Exception
    {
      return ((IFn)const__0.get()).invoke(((IFn)const__1.get()).
             invoke(const__2.get(), coll = null));
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "first");
    public static final Var const__1 = (Var)RT.var("clojure.core", "remove");
    public static final Var const__2 = (Var)RT.var("clojure.core", "nil?");


    public utils$first_non_nil__24()
    {
    }
}
                                              38                                © 2009 Howard Lewis Ship
Compiled to Bytecode
                                (defn first-non-nil
                                  "Returns the first non-nil value from the collection."
                                  [coll]
                                  (first (remove nil? coll)))



package com.howardlewisship.cascade.internal;

import clojure.lang.*;

public class utils$first_non_nil__24 extends AFunction
{

    public Object invoke(Object coll)
      throws Exception
    {
      return ((IFn)const__0.get()).invoke(((IFn)const__1.get()).
             invoke(const__2.get(), coll = null));
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "first");
    public static final Var const__1 = (Var)RT.var("clojure.core", "remove");
    public static final Var const__2 = (Var)RT.var("clojure.core", "nil?");


    public utils$first_non_nil__24()
    {
    }
}
                                              39                                © 2009 Howard Lewis Ship
Compiled to Bytecode
                                (defn first-non-nil
                                  "Returns the first non-nil value from the collection."
                                  [coll]
                                  (first (remove nil? coll)))



package com.howardlewisship.cascade.internal;

import clojure.lang.*;

public class utils$first_non_nil__24 extends AFunction
{

    public Object invoke(Object coll)
      throws Exception
    {
      return ((IFn)const__0.get()).invoke(((IFn)const__1.get()).
             invoke(const__2.get(), coll = null));
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "first");
    public static final Var const__1 = (Var)RT.var("clojure.core", "remove");
    public static final Var const__2 = (Var)RT.var("clojure.core", "nil?");


    public utils$first_non_nil__24()
    {
    }
}
                                              40                                © 2009 Howard Lewis Ship
Compiled to Bytecode
                                    (defn format-date
                                      [env params]
                                      (let [#^Date date (params :date)
package app1;                           #^DateFormat fmt (DateFormat/getDateTimeInstance
                                                   DateFormat/MEDIUM
import clojure.lang.*;                             DateFormat/MEDIUM)]
import java.text.DateFormat;            (.format fmt date)))
import java.util.Date;

public class fragments$format_date__21 extends AFunction
{

    public Object invoke(Object env, Object params)
    throws Exception
    {
      Object date = ((IFn)params).invoke(const__1);
      Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      env = null;
      params = null;
      date = null;
      fmt = null;
      return ((DateFormat)fmt).format((Date)date);
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "let");
    public static final Object const__1 = Keyword.intern(Symbol.create(null, "date"));


    public fragments$format_date__21()
    {
    }
}



                                                  41                                       © 2009 Howard Lewis Ship
Compiled to Bytecode
                                    (defn format-date
                                      [env params]
                                      (let [#^Date date (params :date)
package app1;                           #^DateFormat fmt (DateFormat/getDateTimeInstance
                                                   DateFormat/MEDIUM
import clojure.lang.*;                             DateFormat/MEDIUM)]
import java.text.DateFormat;            (.format fmt date)))
import java.util.Date;

public class fragments$format_date__21 extends AFunction
{

    public Object invoke(Object env, Object params)
    throws Exception
    {
      Object date = ((IFn)params).invoke(const__1);
      Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      env = null;
      params = null;
      date = null;
      fmt = null;
      return ((DateFormat)fmt).format((Date)date);
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "let");
    public static final Object const__1 = Keyword.intern(Symbol.create(null, "date"));


    public fragments$format_date__21()
    {
    }
}



                                                  42                                       © 2009 Howard Lewis Ship
Compiled to Bytecode
                                    (defn format-date
                                      [env params]
                                      (let [#^Date date (params :date)
package app1;                           #^DateFormat fmt (DateFormat/getDateTimeInstance
                                                   DateFormat/MEDIUM
import clojure.lang.*;                             DateFormat/MEDIUM)]
import java.text.DateFormat;            (.format fmt date)))
import java.util.Date;

public class fragments$format_date__21 extends AFunction
{

    public Object invoke(Object env, Object params)
    throws Exception
    {
      Object date = ((IFn)params).invoke(const__1);
      Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      env = null;
      params = null;
      date = null;
      fmt = null;
      return ((DateFormat)fmt).format((Date)date);
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "let");
    public static final Object const__1 = Keyword.intern(Symbol.create(null, "date"));


    public fragments$format_date__21()
    {
    }
}



                                                  43                                       © 2009 Howard Lewis Ship
Compiled to Bytecode
                                    (defn format-date
                                      [env params]
                                      (let [#^Date date (params :date)
package app1;                           #^DateFormat fmt (DateFormat/getDateTimeInstance
                                                   DateFormat/MEDIUM
import clojure.lang.*;                             DateFormat/MEDIUM)]
import java.text.DateFormat;            (.format fmt date)))
import java.util.Date;

public class fragments$format_date__21 extends AFunction
{

    public Object invoke(Object env, Object params)
    throws Exception
    {
      Object date = ((IFn)params).invoke(const__1);
      Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      env = null;
      params = null;
      date = null;
      fmt = null;
      return ((DateFormat)fmt).format((Date)date);
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "let");
    public static final Object const__1 = Keyword.intern(Symbol.create(null, "date"));


    public fragments$format_date__21()
    {
    }
}



                                                  44                                       © 2009 Howard Lewis Ship
Compiled to Bytecode
                                    (defn format-date
                                      [env params]
                                      (let [#^Date date (params :date)
package app1;                           #^DateFormat fmt (DateFormat/getDateTimeInstance
                                                   DateFormat/MEDIUM
import clojure.lang.*;                             DateFormat/MEDIUM)]
import java.text.DateFormat;            (.format fmt date)))
import java.util.Date;

public class fragments$format_date__21 extends AFunction
{

    public Object invoke(Object env, Object params)
    throws Exception
    {
      Object date = ((IFn)params).invoke(const__1);
      Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
      env = null;
      params = null;
      date = null;
      fmt = null;
      return ((DateFormat)fmt).format((Date)date);
    }

    public static final Var const__0 = (Var)RT.var("clojure.core", "let");
    public static final Object const__1 = Keyword.intern(Symbol.create(null, "date"));


    public fragments$format_date__21()
    {
    }
}



                                                  45                                       © 2009 Howard Lewis Ship
Ahead of Time Compilation
        app/fragments.clj

       (ns app.fragments
         (:import (java.util Date)
                  (java.text DateFormat)))

       (defn format-date
         [env params]
         (…))




                            Compiler




        app/fragments__init.class


    app/fragments$format_date__21.class


                                       46    © 2009 Howard Lewis Ship
Ahead Of Time Compilation
 build.xml

    <target name="compile" description="Compile Clojure sources.">
      <mkdir dir="${classes.dir}" />

       <pathconvert pathsep=" " property="compile.namespaces">
         <fileset dir="${src.dir}" includes="**/*.clj" />
         <chainedmapper>
           <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" />
           <filtermapper>
             <replacestring from="_" to="-" />
           </filtermapper>
         </chainedmapper>
       </pathconvert>

      <java classname="clojure.lang.Compile">
        <classpath>
          <path refid="libs.path" />
          <path location="${classes.dir}" />
          <path location="${src.dir}" />
        </classpath>
        <sysproperty key="clojure.compile.path" value="${classes.dir}" />
        <arg line="${compile.namespaces}" />
      </java>
    </target>




                                         47                                 © 2009 Howard Lewis Ship
Ahead Of Time Compilation
 build.xml

    <target name="compile" description="Compile Clojure sources.">
      <mkdir dir="${classes.dir}" />

       <pathconvert pathsep=" " property="compile.namespaces">
         <fileset dir="${src.dir}" includes="**/*.clj" />
         <chainedmapper>
           <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" />
           <filtermapper>
             <replacestring from="_" to="-" />
           </filtermapper>                                      Locate *.clj under
         </chainedmapper>                                       ${src.dir} and convert       to
       </pathconvert>
                                                                 namespace names
      <java classname="clojure.lang.Compile">
        <classpath>
          <path refid="libs.path" />
          <path location="${classes.dir}" />
          <path location="${src.dir}" />
        </classpath>
        <sysproperty key="clojure.compile.path" value="${classes.dir}" />
        <arg line="${compile.namespaces}" />
      </java>
    </target>




                                         48                                 © 2009 Howard Lewis Ship
Ahead Of Time Compilation
 build.xml

    <target name="compile" description="Compile Clojure sources.">
      <mkdir dir="${classes.dir}" />

       <pathconvert pathsep=" " property="compile.namespaces">
         <fileset dir="${src.dir}" includes="**/*.clj" />
         <chainedmapper>
           <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" />
           <filtermapper>
             <replacestring from="_" to="-" />
           </filtermapper>
         </chainedmapper>
       </pathconvert>                                   clojure.jar, source
      <java classname="clojure.lang.Compile">
                                                         directory and output
        <classpath>                                    directory must be on
          <path refid="libs.path" />                   classpath
          <path location="${classes.dir}" />
          <path location="${src.dir}" />
        </classpath>
        <sysproperty key="clojure.compile.path" value="${classes.dir}" />
        <arg line="${compile.namespaces}" />
      </java>
    </target>




                                          49                                    © 2009 Howard Lewis Ship
Ahead Of Time Compilation
 build.xml

    <target name="compile" description="Compile Clojure sources.">
      <mkdir dir="${classes.dir}" />

       <pathconvert pathsep=" " property="compile.namespaces">
         <fileset dir="${src.dir}" includes="**/*.clj" />
         <chainedmapper>
           <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" />
           <filtermapper>
             <replacestring from="_" to="-" />
           </filtermapper>
         </chainedmapper>
       </pathconvert>
                                                  Output directory for
      <java classname="clojure.lang.Compile">     generated classes
        <classpath>
          <path refid="libs.path" />
          <path location="${classes.dir}" />
          <path location="${src.dir}" />
        </classpath>
        <sysproperty key="clojure.compile.path" value="${classes.dir}" />
        <arg line="${compile.namespaces}" />
      </java>
    </target>
                                  List of namespaces to
                                  compile
                                         50                                 © 2009 Howard Lewis Ship
Clojure Pitfalls
       51          © 2009 Howard Lewis Ship
IDE Support




              52   © 2009 Howard Lewis Ship
Laziness

           RuntimeException:
             Divide by zero




             53                © 2009 Howard Lewis Ship
Laziness
Clojure 1.0.0-
1:1 user=> (def nums (map / (range 0 10) (reverse (range 0 10))))
#'user/nums
1:2 user=> nums
java.lang.ArithmeticException: Divide by zero
(0 1/8 2/7 1/2 4/5 5/4 2 7/2 1:3 user=> (.. *e getCause printStackTrace)
java.lang.ArithmeticException: Divide by zero
    at clojure.lang.Numbers.divide(Numbers.java:138)
    at clojure.core$_SLASH___3350.invoke(core.clj:575)
    at clojure.core$map__3815$fn__3822.invoke(core.clj:1508)
    at clojure.lang.LazySeq.seq(LazySeq.java:41)
    at clojure.lang.Cons.next(Cons.java:37)
    at clojure.lang.RT.next(RT.java:560)
    at clojure.core$next__3117.invoke(core.clj:50)
    at clojure.core$nthnext__4405.invoke(core.clj:2531)
    at clojure.core$print_sequential__5354.invoke(core_print.clj:53)
    at clojure.core$fn__5439.invoke(core_print.clj:136)
    at clojure.lang.MultiFn.invoke(MultiFn.java:161)
    at clojure.core$pr_on__4145.invoke(core.clj:2020)
    at clojure.core$pr__4148.invoke(core.clj:2030)
    at clojure.lang.AFn.applyToHelper(AFn.java:173)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply__3243.doInvoke(core.clj:390)
    at clojure.lang.RestFn.invoke(RestFn.java:428)
    at clojure.core$prn__4159.doInvoke(core.clj:2053)
    at clojure.lang.RestFn.invoke(RestFn.java:413)
    at clojure.main$repl__5813$read_eval_print__5825.invoke(main.clj:177)
    at clojure.main$repl__5813.doInvoke(main.clj:193)
    at clojure.lang.RestFn.invoke(RestFn.java:876)
    at clojure.contrib.repl_ln$repl__84.doInvoke(repl_ln.clj:259)
    at clojure.lang.RestFn.invoke(RestFn.java:426)
    at clojure.contrib.repl_ln$_main__44.doInvoke(repl_ln.clj:136)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.contrib.repl_ln.main(Unknown Source)
nil
1:7 user=>

                                                  54                        © 2009 Howard Lewis Ship
Lack of Type Checking




           55           © 2009 Howard Lewis Ship
Spot the Error
view_manager.clj

(defn to-dom-node-seq
  [any]
  "Converts the result of a render function to a seq as needed."
  (cond
    (nil? any) nil

      (sequential? any) any

      ; A map is assumed to be a DOM node, wrap it in a vector
      (map? any) [map]

      (string? any) [(struct-map dom-node :type :text :value any)]

    true (throw (RuntimeException. (format "A rendering function returned %s. ↵
Rendering functions should return nil, a string, a seq of DOM nodes, or a single ↵
DOM node."
    (pr-str any))))))




                                           56                              © 2009 Howard Lewis Ship
Exception

ERROR in (simple-view-and-fragment) (test_is.clj:657)
Uncaught exception, not in assertion.
expected: nil
  actual: java.lang.IllegalArgumentException: Wrong number of args passed to: core$map
 at clojure.lang.AFn.throwArity (AFn.java:449)
    clojure.lang.RestFn.invoke (RestFn.java:417)
    com.howardlewisship.cascade.dom$fn__960.doInvoke (dom.clj:58)
    clojure.lang.RestFn.invoke (RestFn.java:443)
    clojure.lang.MultiFn.invoke (MultiFn.java:165)
    com.howardlewisship.cascade.dom/render_xml_with_ns (dom.clj:118)
    com.howardlewisship.cascade.dom/render_xml (dom.clj:125)
    com.howardlewisship.cascade.test_views/render (test_views.clj:27)
    com.howardlewisship.cascade.test_views/test_view (test_views.clj:56)
    com.howardlewisship.cascade.test_views/test_view (test_views.clj:52)
    com.howardlewisship.cascade.test_views/fn (test_views.clj:62)




                    dom.clj

                    (defmulti render-node-xml
                      (fn [node & rest]
                        (node :type)))




                                         57                                © 2009 Howard Lewis Ship
Spot the Error
view_manager.clj

(defn to-dom-node-seq
  [any]
  "Converts the result of a render function to a seq as needed."
  (cond
    (nil? any) nil

      (sequential? any) any

      ; A map is assumed to be a DOM node, wrap it in a vector
      (map? any) [map]

      (string? any) [(struct-map dom-node :type :text :value any)]

    true (throw (RuntimeException. (format "A rendering function returned %s. ↵
Rendering functions should return nil, a string, a seq of DOM nodes, or a single ↵
DOM node."
    (pr-str any))))))



                      Returning function clojure.core/map;
                      should return any (the parameter)




                                           58                              © 2009 Howard Lewis Ship
Bus Factor




             59   © 2009 Howard Lewis Ship
GitHub Factor




           60   © 2009 Howard Lewis Ship
Performance




          61   © 2009 Howard Lewis Ship
Performance
(defn shorts-to-bytes [#^shorts src #^bytes dst words]
 (loop [src-offset (int 0)
        dst-offset (int 0)]
   (when (< src-offset words)
     (let [sample (short (aget src src-offset))]
       (aset-byte dst dst-offset (byte sample))
       (aset-byte dst (inc dst-offset) (byte (bit-shift-right sample 8))))
     (recur (inc src-offset) (unchecked-add 2 dst-offset)))))



 public static void shortsToBytes(short[] src, byte[] dst, int len)
 {
  int idx = 0;                                                                                 900 ms
  short s;

  while (len-- > 0) {
    s = src[idx];
    dst[idx*2] = (byte)s;                                                                     675 ms
    dst[idx*2+1] = (byte)(s>>>8);
    idx++;
  }
 }
                                                                                             450 ms


                                                                                           225 ms


                                                                                          0 ms

                                                           Java              Clojure

                                                  62                                   © 2009 Howard Lewis Ship
API Docs




           63   © 2009 Howard Lewis Ship
Wrap Up


   64     © 2009 Howard Lewis Ship
What's Not Covered?
• multimethods ➠ inheritance-like function
  dispatch

• Concurrency Support ➠ atoms, agents, refs
  & vars
                                             http://www.clojure.org
• loop & recur ➠ iterative-style coding

• clojure.contrib ➠ Community library

• Macros & Meta-Programming ➠ Create the language you want inside
  Clojure

• Tuning Clojure Performance

• More ….
                                    65                       © 2009 Howard Lewis Ship
Stuart Halloway
                               Pragmatic Bookshelf




http://pragprog.com/titles/shcloj/programming-clojure
                          66                   © 2009 Howard Lewis Ship
http://jnb.ociweb.com/jnb/jnbMar2009.html




                   67                 © 2009 Howard Lewis Ship
Image Credits
   © 2005 Jean-Philippe Daigle
   http://www.flickr.com/photos/jpdaigle/59942231/
                                                        © 2007 Howard Gees
                         http://www.flickr.com/photos/cyberslayer/952121271/
   © 2006 Simon Law
   http://www.flickr.com/photos/sfllaw/222795669/
                                                          © 2008 II-cocoy22-II
                     http://www.flickr.com/photos/30954876@N07/3090155264/
   © 2009 Martin Biskoping
   http://www.flickr.com/photos/mbiskoping/3388639698/
                                                            © 2008 sea turtle
                          http://www.flickr.com/photos/sea-turtle/3049443478/
   © 2009 Tulio Bertorini
   http://www.flickr.com/photos/tuliobertorini/3159130251/
                                                          © 2007 Adam Balch
                       http://www.flickr.com/photos/triplemaximus/795758146/
   © 2008 jessamyn west
   http://www.flickr.com/photos/iamthebestartist/2636607001/




                                     68                                          © 2009 Howard Lewis Ship

Clojure Deep Dive

  • 1.
    Clojure Deep Dive Howard M.Lewis Ship TWD Consulting hlship@gmail.com 1 © 2009 Howard Lewis Ship
  • 2.
    Agenda • Core Language •Standard Tools • Clojure Compilation • Clojure Pitfalls 2 © 2009 Howard Lewis Ship
  • 3.
    Core Language 3 © 2009 Howard Lewis Ship
  • 4.
    What's A Form? Constants: Keywords: Symbols: 42 :foo map "hello" :tag strutils2/take Data Structures: […] {…} #{ … } Callables: Special Forms: Function Call: Macros Expansion: (if …) (map …) (and …) (var …) (or …) (do …) (ns …) 4 © 2009 Howard Lewis Ship
  • 5.
    defn: Define afunction Symbol (in current namespace) Optional doc string (defn to-seq "Converts the object to a sequence (a vector) unless it is already sequential." [obj] (if (sequential? obj) obj [obj])) List of parameters user=> (doc to-seq) ------------------------- user/to-seq ([obj]) Converts the object to a sequence (a vector) unless it is already sequential. nil user=> 5 © 2009 Howard Lewis Ship
  • 6.
    defn: Multiple Arities Onedoc string Series of argument lists / forms (defn not= "Same as (not (= obj1 obj2))" ([x] false) ([x y] (not (= x y))) ([x y & more] (not (apply = x y more)))) & symbol ➠ provide remaining values as a seq user=> (doc not=) ------------------------- clojure.core/not= ([x] [x y] [x y & more]) Same as (not (= obj1 obj2)) nil user=> 6 © 2009 Howard Lewis Ship
  • 7.
    let: local symbols (let [center (calc-center-for-frame frame) x (get-x center) y (get-y center)] …) •Even number: symbol1 form1 symbol2 form2 … •Not variables: symbols are read-only •Can re-define symbols 7 © 2009 Howard Lewis Ship
  • 8.
    do: evaluate forside-effects (if value-found (do (println "Found the value.") value-found)) Last form is the result ; Broken (if value-found (println "Found the value.") If true, side effect & return nil value-found) If false, return value (false or nil) 8 © 2009 Howard Lewis Ship
  • 9.
    Branching: if, when (if test then-form) (if test then-form else-form) • Special form: not a function • Evaluates test, then evaluates & returns either then-form or else-form • false and nil ➠ logical false • anything else ➠ logical true (when test then-forms) (if-not test then-form) (if-not test then-form else-form) Any number of forms in an implicit do Like (if (not test) then else) (when-not test then-forms) 9 © 2009 Howard Lewis Ship
  • 10.
    if-let, when-let (if-let [symboltest] then-form) (if-let [symbol test] then-form else-form) Symbol can be referenced (when-let [symbol test] then-forms) Any number of forms in an implicit do 10 © 2009 Howard Lewis Ship
  • 11.
    or, and (or form…) (and form…) • or: first value from list that evaluates to true • Will return value of last expression (false or nil) • (or) returns nil • and: first logical false (nil or false) • Returns value of last expression (if prior are logical true) • (and) return true • Can be used as a "guard" Only invoke symbol and find-ns (defn dispatch-named-function-to-pipeline if ns-name is non-nil [env pipeline] (let [split-path (-> env :cascade :split-path) [_ ns-name fn-name] split-path fn-namespace (and ns-name (find-ns (symbol ns-name))) named-function (and fn-namespace fn-name (ns-resolve fn-namespace (symbol fn-name))) new-env (assoc-in env [:cascade :extra-path] (drop 3 split-path))] (call-pipeline pipeline new-env named-function))) 11 © 2009 Howard Lewis Ship
  • 12.
    Destructuring Bu N wo sy o rk • Automatically extract values from data structures ! • Binding forms: • Map to extract keys • Vector to extract indexed values • Function parameters One un-named parameter • let, if-let, when-let, etc. destructured to two symbols user=> (defn natural-name [{fname :first-name lname :last-name}] (str lname ", " fname)) #'user/natural-name user=> (natural-name (persons 1)) "Simon, Scott" user=> 12 © 2009 Howard Lewis Ship
  • 13.
    Destructuring • Binding Forms: • symbol (let [x (get-x center)] … ) • vector of binding forms (let [[x y] center] … ) • map of binding form to map key (let [{fname :first-name lname :last-name} person] …) 13 © 2009 Howard Lewis Ship
  • 14.
    Vector Destructuring center must be sequential (let [[x y] center] …) May be nil if not enough values in center (let [[first-born second-born & other-children] children] …) & symbol gets the remaining values (let [[first-born second-born & other-children :as children] (find-children parent)] …) Nested binding forms! :as symbol gets the full list user=> (let [[[x1 y1][x2 y2]] [[1 2] [3 4]]] [x1 y1 x2 y2]) [1 2 3 4] user=> 14 © 2009 Howard Lewis Ship
  • 15.
    Map Destructuring (defn natural-name[{fname :first-name lname :last-name}] (str lname ", " fname)) (defn natural-name [{:keys [first-name last-name]}] (str last-name ", " first-name)) :keys maps a keyword key for each symbol :or sets defaults for missing keys user=> (defn natural-name [{:keys [first-name last-name] :or {first-name "(none)" last-name "(none)"}}] (str last-name ", " first-name)) #'user/natural-name user=> (natural-name {:first-name "Gromit"}) "(none), Gromit" user=> (natural-name {:first-name "Gromit" :last-name nil}) ", Gromit" user=> :as symbol to get the original map :strs ➠ like :keys, but maps to string keys, not keyword keys :syms ➠ like :keys, but maps to symbol keys, not keyword keys 15 © 2009 Howard Lewis Ship
  • 16.
    ns: define namespaces (nsexample.utils) cascade/dispatcher.clj (ns cascade.dispatcher (:import (javax.servlet ServletResponse)) File: example/utils.clj (:use (cascade config dom logging path-map pipeline) (cascade.internal utils))) (ns namespace (:import …) Optional directives (:use …)) user=> (defn natural-name [{fname :first-name lname :last-name}] (str lname ", " fname)) #'user/natural-name user=> (natural-name (persons 1)) "Simon, Scott" user=> user/natural-name Namespace: user Symbol: natural-name 16 © 2009 Howard Lewis Ship
  • 17.
    :import Java Classes Package name, then any number of classes/interfaces (ns cascade.dispatcher (:import (javax.servlet ServletResponse)) …) Repeat for other packages Can also list fully qualify class names 17 © 2009 Howard Lewis Ship
  • 18.
    :use Import Namespaces (:useclojure.contrib.pprint) Symbols defined by pprint are available, i.e. (pprint [1 2 3]) (:use (clojure.contrib pprint monads)) Add multiple namespaces under the root namespace (:use (clojure.contrib (str-utils2 :only [join map-str])) Uses just join and map-str from str-utils2 (:use (clojure.contrib (str-utils2 :exclude [take replace drop])) Exclude just some of the symbols (:use (clojure.contrib (str-utils2 :rename {take strtake})) Use str-utils2, mapping take as strtake 18 © 2009 Howard Lewis Ship
  • 19.
    Other ns directives •:require ➠ load a namespace but don't import it • :gen-class ➠ create a Java class from functions in the namespace • :refer-clojure ➠ import selected clojure.core symbols • :load ➠ load Clojure scripts (not namespaces) 19 © 2009 Howard Lewis Ship
  • 20.
    Standard Tools 20 © 2009 Howard Lewis Ship
  • 21.
    Anonymous Functions #() ➠ implicit anonymous function (filter #(not (.startsWith % ".")) names) % is replaced by the function's parameter %n is parameter #n (map #(assoc %1 :sort-index %2) (sort-by :age persons) (iterate inc 0)) inline anonymous function (map 21 (fn [person-map sort-index] (assoc person-map :sort-index sort-index)) (sort-by :age persons) (iterate inc 0)) © 2009 Howard Lewis Ship
  • 22.
    What are seqs? Seqable seq() : ISeq IPersistentCollection Sequential count() : int cons(Object): IPersistentCollection empty() : IPersistentCollection equiv(Object) : boolean PersistentSet ISeq first() : Object next(): ISeq more(): ISeq cons(Object): ISeq Returned from map, for, filter, remove, etc. PersistentList PersistentVector LazySeq 22 © 2009 Howard Lewis Ship
  • 23.
    assoc-in / update-in user=>(def person { :first-name "Howard" :last-name "Lewis Ship" :address { :street "123 NW 12th Ave. #541" :city "Portland" :state "OR" :zip 97309 }}) #'user/person user=> (assoc-in person [:address :street] "2647 SE 97th Ave.") {:first-name "Howard", :last-name "Lewis Ship", :address {:street "2647 SE 97th Ave.", :city "Portland", :state "OR", :zip 97309}} user=> (update-in person [:address :zip] + 5) {:first-name "Howard", :last-name "Lewis Ship", :address {:street "123 NW 12th Ave. #541", :city "Portland", :state "OR", :zip 97314}} user=> :first-name "Howard" :last-name "Lewis Ship" :address :street "123 NW 12th Ave." :city "Portland" :state "OR" :zip 97309 (update-in person [:address :zip] + 5) (+ 97309 5) :first-name "Howard" :last-name "Lewis Ship" :address :street "123 NW 12th Ave." :city "Portland" :state "OR" :zip 97314 23 © 2009 Howard Lewis Ship
  • 24.
    map La zy f :age acts as a function user=> (map :age persons) (42 44 29) user=> (map #(apply str (reverse (% :first-name))) persons) ("drawoH" "ttocS" "ylloM") user=> (map identity (persons 0)) ([:first-name "Howard"] iterate a map ➠ key/value pairs [:last-name "Lewis Ship"] [:age 42]) user=> (seq (persons 0)) ([:first-name "Howard"] [:last-name "Lewis Ship"] [:age 42]) user=> 24 © 2009 Howard Lewis Ship
  • 25.
    map La zy f N seqs ➠ N parameters user=> (map #(assoc %1 :sort-index %2) (sort-by :age persons) (iterate inc 0)) ({:sort-index 0, :first-name "Molly", :last-name "Newman", :age 29} {:sort-index 1, :first-name "Howard", :last-name "Lewis Ship", :age 42} {:sort-index 2, :first-name "Scott", :last-name "Simon", :age 44}) user=> 25 © 2009 Howard Lewis Ship
  • 26.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) 26 © 2009 Howard Lewis Ship
  • 27.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) (reduce + '(42 44 29 38)) 27 © 2009 Howard Lewis Ship
  • 28.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) (reduce + '(42 44 29 38)) (+ (+ (+ 42 44) 29) 38) 28 © 2009 Howard Lewis Ship
  • 29.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) (reduce + '(42 44 29 38)) (+ (+ (+ 42 44) 29) 38) (+ (+ 86 29) 38) 29 © 2009 Howard Lewis Ship
  • 30.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) (reduce + '(42 44 29 38)) (+ (+ (+ 42 44) 29) 38) (+ (+ 86 29) 38) (+ 115 38) 30 © 2009 Howard Lewis Ship
  • 31.
    reduce f user=> (map :age persons) (42 44 29 38) user=> (reduce + (map :age persons)) 153 (reduce + (map :age persons)) (reduce + '(42 44 29 38)) (+ (+ (+ 42 44) 29) 38) (+ (+ 86 29) 38) (+ 115 38) 153 31 © 2009 Howard Lewis Ship
  • 32.
    reduce f user=> (def input-string "Clojure is a fascinating language with unique capabilities and total integration with Java.") #'user/input-string user=> (seq input-string) (C l o j u r e space i s space a space f a s c i n a t i n g space l a n g u a g e space w i t h space u n i q u e space c a p a b i l i t i e s space a n d space t o t a l space i n t e g r a t i o n space w i t h space J a v a .) user=> (reduce (fn [m k] (update-in m [k] #(if (nil? %) 1 (inc %)))) Optional initial value {} (seq input-string)) {space 12, a 12, b 1, C 1, c 2, d 1, e 5, f 1, g 4, h 2, i 11, J 1, j 1, l 4, . 1, n 7, o 3, p 1, q 1, r 2, s 3, t 8, u 4, v 1, w 2} user=> 32 © 2009 Howard Lewis Ship
  • 33.
    filter / remove La zy f ? user=> (remove #(< (% :age) 30) persons) ({:first-name "Howard", :last-name "Lewis Ship", :age 42} {:first-name "Scott", :last-name "Simon", :age 44}) user=> (filter #(< (% :age) 30) persons) ({:first-name "Molly", :last-name "Newman", :age 29}) user=> 33 © 2009 Howard Lewis Ship
  • 34.
    apply f user=> (map :age persons) (42 44 29) user=> (apply + (map :age persons)) 115 user=> (apply + (map :age persons)) (apply + '(42 44 29)) (+ 42 44 29) 115 34 © 2009 Howard Lewis Ship
  • 35.
    Clojure Compilation 35 © 2009 Howard Lewis Ship
  • 36.
    Function Calls (remove nil? coll) IFn invoke() : Object invoke(Object) : Object Namespace invoke(Object, Object) : Object … applyTo(ISeq) : Object Var get() clojure.core$remove__4782 PersistentMap Keyword 36 © 2009 Howard Lewis Ship
  • 37.
    Compiled to Bytecode (defn first-non-nil "Returns the first non-nil value from the collection." [coll] (first (remove nil? coll))) package com.howardlewisship.cascade.internal; import clojure.lang.*; public class utils$first_non_nil__24 extends AFunction { public Object invoke(Object coll) throws Exception { return ((IFn)const__0.get()).invoke(((IFn)const__1.get()). invoke(const__2.get(), coll = null)); } public static final Var const__0 = (Var)RT.var("clojure.core", "first"); public static final Var const__1 = (Var)RT.var("clojure.core", "remove"); public static final Var const__2 = (Var)RT.var("clojure.core", "nil?"); public utils$first_non_nil__24() { } } 37 © 2009 Howard Lewis Ship
  • 38.
    Compiled to Bytecode (defn first-non-nil "Returns the first non-nil value from the collection." [coll] (first (remove nil? coll))) package com.howardlewisship.cascade.internal; import clojure.lang.*; public class utils$first_non_nil__24 extends AFunction { public Object invoke(Object coll) throws Exception { return ((IFn)const__0.get()).invoke(((IFn)const__1.get()). invoke(const__2.get(), coll = null)); } public static final Var const__0 = (Var)RT.var("clojure.core", "first"); public static final Var const__1 = (Var)RT.var("clojure.core", "remove"); public static final Var const__2 = (Var)RT.var("clojure.core", "nil?"); public utils$first_non_nil__24() { } } 38 © 2009 Howard Lewis Ship
  • 39.
    Compiled to Bytecode (defn first-non-nil "Returns the first non-nil value from the collection." [coll] (first (remove nil? coll))) package com.howardlewisship.cascade.internal; import clojure.lang.*; public class utils$first_non_nil__24 extends AFunction { public Object invoke(Object coll) throws Exception { return ((IFn)const__0.get()).invoke(((IFn)const__1.get()). invoke(const__2.get(), coll = null)); } public static final Var const__0 = (Var)RT.var("clojure.core", "first"); public static final Var const__1 = (Var)RT.var("clojure.core", "remove"); public static final Var const__2 = (Var)RT.var("clojure.core", "nil?"); public utils$first_non_nil__24() { } } 39 © 2009 Howard Lewis Ship
  • 40.
    Compiled to Bytecode (defn first-non-nil "Returns the first non-nil value from the collection." [coll] (first (remove nil? coll))) package com.howardlewisship.cascade.internal; import clojure.lang.*; public class utils$first_non_nil__24 extends AFunction { public Object invoke(Object coll) throws Exception { return ((IFn)const__0.get()).invoke(((IFn)const__1.get()). invoke(const__2.get(), coll = null)); } public static final Var const__0 = (Var)RT.var("clojure.core", "first"); public static final Var const__1 = (Var)RT.var("clojure.core", "remove"); public static final Var const__2 = (Var)RT.var("clojure.core", "nil?"); public utils$first_non_nil__24() { } } 40 © 2009 Howard Lewis Ship
  • 41.
    Compiled to Bytecode (defn format-date [env params] (let [#^Date date (params :date) package app1; #^DateFormat fmt (DateFormat/getDateTimeInstance DateFormat/MEDIUM import clojure.lang.*; DateFormat/MEDIUM)] import java.text.DateFormat; (.format fmt date))) import java.util.Date; public class fragments$format_date__21 extends AFunction { public Object invoke(Object env, Object params) throws Exception { Object date = ((IFn)params).invoke(const__1); Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); env = null; params = null; date = null; fmt = null; return ((DateFormat)fmt).format((Date)date); } public static final Var const__0 = (Var)RT.var("clojure.core", "let"); public static final Object const__1 = Keyword.intern(Symbol.create(null, "date")); public fragments$format_date__21() { } } 41 © 2009 Howard Lewis Ship
  • 42.
    Compiled to Bytecode (defn format-date [env params] (let [#^Date date (params :date) package app1; #^DateFormat fmt (DateFormat/getDateTimeInstance DateFormat/MEDIUM import clojure.lang.*; DateFormat/MEDIUM)] import java.text.DateFormat; (.format fmt date))) import java.util.Date; public class fragments$format_date__21 extends AFunction { public Object invoke(Object env, Object params) throws Exception { Object date = ((IFn)params).invoke(const__1); Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); env = null; params = null; date = null; fmt = null; return ((DateFormat)fmt).format((Date)date); } public static final Var const__0 = (Var)RT.var("clojure.core", "let"); public static final Object const__1 = Keyword.intern(Symbol.create(null, "date")); public fragments$format_date__21() { } } 42 © 2009 Howard Lewis Ship
  • 43.
    Compiled to Bytecode (defn format-date [env params] (let [#^Date date (params :date) package app1; #^DateFormat fmt (DateFormat/getDateTimeInstance DateFormat/MEDIUM import clojure.lang.*; DateFormat/MEDIUM)] import java.text.DateFormat; (.format fmt date))) import java.util.Date; public class fragments$format_date__21 extends AFunction { public Object invoke(Object env, Object params) throws Exception { Object date = ((IFn)params).invoke(const__1); Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); env = null; params = null; date = null; fmt = null; return ((DateFormat)fmt).format((Date)date); } public static final Var const__0 = (Var)RT.var("clojure.core", "let"); public static final Object const__1 = Keyword.intern(Symbol.create(null, "date")); public fragments$format_date__21() { } } 43 © 2009 Howard Lewis Ship
  • 44.
    Compiled to Bytecode (defn format-date [env params] (let [#^Date date (params :date) package app1; #^DateFormat fmt (DateFormat/getDateTimeInstance DateFormat/MEDIUM import clojure.lang.*; DateFormat/MEDIUM)] import java.text.DateFormat; (.format fmt date))) import java.util.Date; public class fragments$format_date__21 extends AFunction { public Object invoke(Object env, Object params) throws Exception { Object date = ((IFn)params).invoke(const__1); Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); env = null; params = null; date = null; fmt = null; return ((DateFormat)fmt).format((Date)date); } public static final Var const__0 = (Var)RT.var("clojure.core", "let"); public static final Object const__1 = Keyword.intern(Symbol.create(null, "date")); public fragments$format_date__21() { } } 44 © 2009 Howard Lewis Ship
  • 45.
    Compiled to Bytecode (defn format-date [env params] (let [#^Date date (params :date) package app1; #^DateFormat fmt (DateFormat/getDateTimeInstance DateFormat/MEDIUM import clojure.lang.*; DateFormat/MEDIUM)] import java.text.DateFormat; (.format fmt date))) import java.util.Date; public class fragments$format_date__21 extends AFunction { public Object invoke(Object env, Object params) throws Exception { Object date = ((IFn)params).invoke(const__1); Object fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); env = null; params = null; date = null; fmt = null; return ((DateFormat)fmt).format((Date)date); } public static final Var const__0 = (Var)RT.var("clojure.core", "let"); public static final Object const__1 = Keyword.intern(Symbol.create(null, "date")); public fragments$format_date__21() { } } 45 © 2009 Howard Lewis Ship
  • 46.
    Ahead of TimeCompilation app/fragments.clj (ns app.fragments (:import (java.util Date) (java.text DateFormat))) (defn format-date [env params] (…)) Compiler app/fragments__init.class app/fragments$format_date__21.class 46 © 2009 Howard Lewis Ship
  • 47.
    Ahead Of TimeCompilation build.xml <target name="compile" description="Compile Clojure sources."> <mkdir dir="${classes.dir}" /> <pathconvert pathsep=" " property="compile.namespaces"> <fileset dir="${src.dir}" includes="**/*.clj" /> <chainedmapper> <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" /> <filtermapper> <replacestring from="_" to="-" /> </filtermapper> </chainedmapper> </pathconvert> <java classname="clojure.lang.Compile"> <classpath> <path refid="libs.path" /> <path location="${classes.dir}" /> <path location="${src.dir}" /> </classpath> <sysproperty key="clojure.compile.path" value="${classes.dir}" /> <arg line="${compile.namespaces}" /> </java> </target> 47 © 2009 Howard Lewis Ship
  • 48.
    Ahead Of TimeCompilation build.xml <target name="compile" description="Compile Clojure sources."> <mkdir dir="${classes.dir}" /> <pathconvert pathsep=" " property="compile.namespaces"> <fileset dir="${src.dir}" includes="**/*.clj" /> <chainedmapper> <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" /> <filtermapper> <replacestring from="_" to="-" /> </filtermapper> Locate *.clj under </chainedmapper> ${src.dir} and convert to </pathconvert> namespace names <java classname="clojure.lang.Compile"> <classpath> <path refid="libs.path" /> <path location="${classes.dir}" /> <path location="${src.dir}" /> </classpath> <sysproperty key="clojure.compile.path" value="${classes.dir}" /> <arg line="${compile.namespaces}" /> </java> </target> 48 © 2009 Howard Lewis Ship
  • 49.
    Ahead Of TimeCompilation build.xml <target name="compile" description="Compile Clojure sources."> <mkdir dir="${classes.dir}" /> <pathconvert pathsep=" " property="compile.namespaces"> <fileset dir="${src.dir}" includes="**/*.clj" /> <chainedmapper> <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" /> <filtermapper> <replacestring from="_" to="-" /> </filtermapper> </chainedmapper> </pathconvert> clojure.jar, source <java classname="clojure.lang.Compile"> directory and output <classpath> directory must be on <path refid="libs.path" /> classpath <path location="${classes.dir}" /> <path location="${src.dir}" /> </classpath> <sysproperty key="clojure.compile.path" value="${classes.dir}" /> <arg line="${compile.namespaces}" /> </java> </target> 49 © 2009 Howard Lewis Ship
  • 50.
    Ahead Of TimeCompilation build.xml <target name="compile" description="Compile Clojure sources."> <mkdir dir="${classes.dir}" /> <pathconvert pathsep=" " property="compile.namespaces"> <fileset dir="${src.dir}" includes="**/*.clj" /> <chainedmapper> <packagemapper from="${basedir}/${src.dir}/*.clj" to="*" /> <filtermapper> <replacestring from="_" to="-" /> </filtermapper> </chainedmapper> </pathconvert> Output directory for <java classname="clojure.lang.Compile"> generated classes <classpath> <path refid="libs.path" /> <path location="${classes.dir}" /> <path location="${src.dir}" /> </classpath> <sysproperty key="clojure.compile.path" value="${classes.dir}" /> <arg line="${compile.namespaces}" /> </java> </target> List of namespaces to compile 50 © 2009 Howard Lewis Ship
  • 51.
    Clojure Pitfalls 51 © 2009 Howard Lewis Ship
  • 52.
    IDE Support 52 © 2009 Howard Lewis Ship
  • 53.
    Laziness RuntimeException: Divide by zero 53 © 2009 Howard Lewis Ship
  • 54.
    Laziness Clojure 1.0.0- 1:1 user=>(def nums (map / (range 0 10) (reverse (range 0 10)))) #'user/nums 1:2 user=> nums java.lang.ArithmeticException: Divide by zero (0 1/8 2/7 1/2 4/5 5/4 2 7/2 1:3 user=> (.. *e getCause printStackTrace) java.lang.ArithmeticException: Divide by zero at clojure.lang.Numbers.divide(Numbers.java:138) at clojure.core$_SLASH___3350.invoke(core.clj:575) at clojure.core$map__3815$fn__3822.invoke(core.clj:1508) at clojure.lang.LazySeq.seq(LazySeq.java:41) at clojure.lang.Cons.next(Cons.java:37) at clojure.lang.RT.next(RT.java:560) at clojure.core$next__3117.invoke(core.clj:50) at clojure.core$nthnext__4405.invoke(core.clj:2531) at clojure.core$print_sequential__5354.invoke(core_print.clj:53) at clojure.core$fn__5439.invoke(core_print.clj:136) at clojure.lang.MultiFn.invoke(MultiFn.java:161) at clojure.core$pr_on__4145.invoke(core.clj:2020) at clojure.core$pr__4148.invoke(core.clj:2030) at clojure.lang.AFn.applyToHelper(AFn.java:173) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply__3243.doInvoke(core.clj:390) at clojure.lang.RestFn.invoke(RestFn.java:428) at clojure.core$prn__4159.doInvoke(core.clj:2053) at clojure.lang.RestFn.invoke(RestFn.java:413) at clojure.main$repl__5813$read_eval_print__5825.invoke(main.clj:177) at clojure.main$repl__5813.doInvoke(main.clj:193) at clojure.lang.RestFn.invoke(RestFn.java:876) at clojure.contrib.repl_ln$repl__84.doInvoke(repl_ln.clj:259) at clojure.lang.RestFn.invoke(RestFn.java:426) at clojure.contrib.repl_ln$_main__44.doInvoke(repl_ln.clj:136) at clojure.lang.RestFn.applyTo(RestFn.java:142) at clojure.contrib.repl_ln.main(Unknown Source) nil 1:7 user=> 54 © 2009 Howard Lewis Ship
  • 55.
    Lack of TypeChecking 55 © 2009 Howard Lewis Ship
  • 56.
    Spot the Error view_manager.clj (defnto-dom-node-seq [any] "Converts the result of a render function to a seq as needed." (cond (nil? any) nil (sequential? any) any ; A map is assumed to be a DOM node, wrap it in a vector (map? any) [map] (string? any) [(struct-map dom-node :type :text :value any)] true (throw (RuntimeException. (format "A rendering function returned %s. ↵ Rendering functions should return nil, a string, a seq of DOM nodes, or a single ↵ DOM node." (pr-str any)))))) 56 © 2009 Howard Lewis Ship
  • 57.
    Exception ERROR in (simple-view-and-fragment)(test_is.clj:657) Uncaught exception, not in assertion. expected: nil actual: java.lang.IllegalArgumentException: Wrong number of args passed to: core$map at clojure.lang.AFn.throwArity (AFn.java:449) clojure.lang.RestFn.invoke (RestFn.java:417) com.howardlewisship.cascade.dom$fn__960.doInvoke (dom.clj:58) clojure.lang.RestFn.invoke (RestFn.java:443) clojure.lang.MultiFn.invoke (MultiFn.java:165) com.howardlewisship.cascade.dom/render_xml_with_ns (dom.clj:118) com.howardlewisship.cascade.dom/render_xml (dom.clj:125) com.howardlewisship.cascade.test_views/render (test_views.clj:27) com.howardlewisship.cascade.test_views/test_view (test_views.clj:56) com.howardlewisship.cascade.test_views/test_view (test_views.clj:52) com.howardlewisship.cascade.test_views/fn (test_views.clj:62) dom.clj (defmulti render-node-xml (fn [node & rest] (node :type))) 57 © 2009 Howard Lewis Ship
  • 58.
    Spot the Error view_manager.clj (defnto-dom-node-seq [any] "Converts the result of a render function to a seq as needed." (cond (nil? any) nil (sequential? any) any ; A map is assumed to be a DOM node, wrap it in a vector (map? any) [map] (string? any) [(struct-map dom-node :type :text :value any)] true (throw (RuntimeException. (format "A rendering function returned %s. ↵ Rendering functions should return nil, a string, a seq of DOM nodes, or a single ↵ DOM node." (pr-str any)))))) Returning function clojure.core/map; should return any (the parameter) 58 © 2009 Howard Lewis Ship
  • 59.
    Bus Factor 59 © 2009 Howard Lewis Ship
  • 60.
    GitHub Factor 60 © 2009 Howard Lewis Ship
  • 61.
    Performance 61 © 2009 Howard Lewis Ship
  • 62.
    Performance (defn shorts-to-bytes [#^shortssrc #^bytes dst words]  (loop [src-offset (int 0)         dst-offset (int 0)]    (when (< src-offset words)      (let [sample (short (aget src src-offset))]        (aset-byte dst dst-offset (byte sample))        (aset-byte dst (inc dst-offset) (byte (bit-shift-right sample 8))))      (recur (inc src-offset) (unchecked-add 2 dst-offset)))))  public static void shortsToBytes(short[] src, byte[] dst, int len)  {   int idx = 0; 900 ms   short s;   while (len-- > 0) {     s = src[idx];     dst[idx*2] = (byte)s; 675 ms     dst[idx*2+1] = (byte)(s>>>8);     idx++;   }  } 450 ms 225 ms 0 ms Java Clojure 62 © 2009 Howard Lewis Ship
  • 63.
    API Docs 63 © 2009 Howard Lewis Ship
  • 64.
    Wrap Up 64 © 2009 Howard Lewis Ship
  • 65.
    What's Not Covered? •multimethods ➠ inheritance-like function dispatch • Concurrency Support ➠ atoms, agents, refs & vars http://www.clojure.org • loop & recur ➠ iterative-style coding • clojure.contrib ➠ Community library • Macros & Meta-Programming ➠ Create the language you want inside Clojure • Tuning Clojure Performance • More …. 65 © 2009 Howard Lewis Ship
  • 66.
    Stuart Halloway Pragmatic Bookshelf http://pragprog.com/titles/shcloj/programming-clojure 66 © 2009 Howard Lewis Ship
  • 67.
    http://jnb.ociweb.com/jnb/jnbMar2009.html 67 © 2009 Howard Lewis Ship
  • 68.
    Image Credits © 2005 Jean-Philippe Daigle http://www.flickr.com/photos/jpdaigle/59942231/ © 2007 Howard Gees http://www.flickr.com/photos/cyberslayer/952121271/ © 2006 Simon Law http://www.flickr.com/photos/sfllaw/222795669/ © 2008 II-cocoy22-II http://www.flickr.com/photos/30954876@N07/3090155264/ © 2009 Martin Biskoping http://www.flickr.com/photos/mbiskoping/3388639698/ © 2008 sea turtle http://www.flickr.com/photos/sea-turtle/3049443478/ © 2009 Tulio Bertorini http://www.flickr.com/photos/tuliobertorini/3159130251/ © 2007 Adam Balch http://www.flickr.com/photos/triplemaximus/795758146/ © 2008 jessamyn west http://www.flickr.com/photos/iamthebestartist/2636607001/ 68 © 2009 Howard Lewis Ship