Clojure Notes

francis.rammeloo@gmail.com

Credits

These are my notes while learning Clojure from the book Programming Clojure by Stuart Halloway. 

Starting Clojure

Windows shortcut for starting REPL:

Target:

"%ProgramFiles%\Java\jre6\bin\java.exe" -cp clojure.jar clojure.main

Start in:

C:\clojure-1.1.0

Ubuntu installation instructions:

http://riddell.us/ClojureOnUbuntu.html

REPL + Startup script:

java -cp clojure.jar clojure.main -i startup.clj -r

Launch a Clojure application:

java -cp clojure.jar clojure.main -i myscript.clj

Startup script contents:

(use 'clojure.contrib.repl-utils)

REPL

Import REPL utils:

(use 'clojure.contrib.repl-utils)Show documentation for a function:

(doc show)

(doc import)

(doc yourfunction)

Find documentation with string pattern:

(find-doc "sho")

Show functions in a namespace:

(keys (ns-publics 'clojure.core))

Basic stuff

Hello world:

(println "Hello world")

Comments:

; this is a comment

(println "Hello world") ; prints hello world

Define a value:

(def age 29)

(def firstname "Francis")

Print a value:

(print age)

(age) ; shown in  REPL only

Basic math:

(+ 3 4) ; yields 7

(- 3 4) ; yields -1

(* 3 4) ; yields 12

(/ 3 4) ; yields 3/4 (fraction type)

; Using named values

(def a 3)

(def b 4)

(+ a b)

; increment

(inc 1)

; decrement

(dec 1) 

(def i 1)

(inc i) ; returns i + 1 (i remains umodified)

Casting:

; convert fraction to int

(int (/ 3 2)) ; yields 1

; convert int to boolean

(boolean 1)  ; yields true

Functions

Define a function:

(defn sum [a b] (+ a b))

(defn say-hello [name] (println "Hello" name))

Invoke a function:

(sum 3 4)

(say-hello "John")

Overloading by number of arguments:

(defn sum ([a b] (+ a b))

              ([a b c] (+ a (+ b c))))

; call it

(sum 2 3) ; yields 5

(sum 1 2 3) ; yields 6

Anonymous function:

(fn [a b] (+ a b))

; example

(println ((fn [a b] (+ a b)) 3 4))

; Shorter notation

#(+ %1 %2) 

; example

(println (#(+ %1 %2) 3 4))

Using let for local values:

(defn circle-area [radius]

  (let [pi 3.141593

            square (fn [x] (* x x ))]

           (* pi (square radius))))

; example

(circle-area 2) ; yields 12.566372

Logic

If smaller than:

(if (< 3 4) (println "true") (println "false"))

If greater than:

(if (> 3 4) (println "true") (println "false"))

If equal:

(if (= 3 4) (println "true") (println "false"))

(if (identical? 3 4) (println "true") (println "false"))

If not equal:

(if (not (= 3 4)) (println "true") (println "false"))

Equal to zero:

(zero? 0) ; yields true

Iteration

Print numbers from 0 to 9:

; using dotimes

(dotimes [i 10] (println i))

; using loop and recur

(loop [i 0]

  (if (< i 10)

        (do (println i)

            (recur (inc i)))))

; shorter notation

(loop [i 0]

  (when (< i 10)

        (println i)

        (recur (inc i))))

Add numbers from 0 to n:

(defn sum-to [n]

  (loop [i 1 sum 0]

        (if (<= i n)

          (recur (inc i) (+ i sum))

          sum)))

Start an infinite loop (don't do this):

(loop [] (recur))

Data structures

List

; create list

(list 1 2 3)

; shorter notation

'(1 2 3)

(count '(1 2 3)) ; yields 3

Vector

; create vector

[1 2 3]

; length

(count [1 2 3]) ; yields 3

; access by index

(nth ["a" "b" "c"] 1) ; yields "b"

; iteration by index (for loop)

(def primes [2 3 5 7 11 13 17 19])

(dotimes [i (count primes)]

        (println (nth primes i)))

; sub-vector (interval)

(def v [ 0 1 2 3 4 5 ])

(subvec v 1 4) ; yields [ 1 2 3 ]

; concatenate (version 1)

(def v1 [ 1 2 3 ])

(def v2 [ 4 5 6 ])

(reduce conj v1 v2)

=> [1 2 3 4 5 6]

; concatenate (version 2)

(into v1 v2)

=> [1 2 3 4 5 6]

; concatenate (version 3, yields a seq)

(concat v1 v2)

=> (1 2 3 4 5 6)

; concatenate (version 4, yields a vector)

(into [] (concat v1 v2))

=> [1 2 3 4 5 6]

Map

; map number to string

{ 0 "zero", 1 "one", 2 "two" }

; shorter notation (no commas)

{ 0 "zero" 1 "one" 2 "two" }

; example

(def person {:first-name "Thomas" :last-name "Anderson"})

(println (person :first-name))

(println (person :last-name))

Set

; create set

(set [:do :re :mi])

; shorter notation

#{:do :re :mi}

; see "Sequences" for examples on how to use sets

Struct

; define person type

(defstruct person :first-name :last-name)

; instantiate a person

(def p (struct person "Thomas" "Anderson"))

; get properties

(p :first-name) ; yields "Thomas"

(:first-name p) ; also yields "Thomas"

; use struct-map to instantiate with named arguments

(struct-map person :last-name "Anderson" :first-name "Thomas")

; .. or even with additional fields

(struct-map person  :last-name "Anderson" :first-name "Thomas" :age 36)

Metadata

Example:

; define color type

(defstruct color :r :g :b)

; instantiate a color object with metadata

(def white (with-meta (struct color 255 255 255) {:object-id 11}))

; get data

(white :r)

; get metadata

((meta white) :object-id)

Namespaces

Create and enter a new namespace:

(in-ns 'playground)

Import the core namespace in the new namespace (do this!):

(clojure.core/use 'clojure.core)

Import a normal namespace:

(use 'clojure.contrib.math)

Reload a namespace:

(use :reload 'clojure.contrib.math)

Reload a namespace and any namespace it refers to:

(use :reload-all 'clojure.contrib.math)

References

References allow mutating state within a transaction.

Create a reference:

(def i (ref 0))

(def text (ref "hello"))

; empty reference

(def i (ref ())

Dereferencing:

(println (deref i ))

(println (deref text))

; shorter notation

(println @i)

(println @text)

Changing state:

; dosync + ref-set

(dosync (ref-set i 1)) ; set i = 1

; dosync + alter

(dosync (alter i inc)) ; increment i

(dosync (alter text (fn [s] (.toUpperCase s))))

Java basics

Importing Java classes:

; single import

(import javax.swing.JFrame)

; multiple imports from one package

(import '(javax.swing JFrame JButton))

; importing fom multiple packages

(import '(javax.swing JFrame JButton)

            '(java.awt.Graphics))

Creating objects:

(import java.util.Random)

; constructor

(new Random)

; shorter notation (syntactic sugar)

(Random.)

Invoking methods:

; constructor

(def rnd (new java.util.Random))

; Java: rnd.nextInt(10)

(. rnd nextInt 10)

; shorter notation (syntactic sugar)

(.nextInt rnd 10)

Invoking static methods:

(import java.awt.Color)

; call Color.BLUE or Math.PI

(. Color BLUE)

(. Math PI)

; shorter notation (syntactic sugar)

(Color/BLUE)

(Math/PI)

Java Basics

Create and manipulate arrays:

(def rgb-array (make-array Integer 3))

(aset rgb-array 0 0)

(aset rgb-array 1 0)

(aset rgb-array 2 255)

(aget rgb-array 0) ; yields 0

(aget rgb-array 1) ; yields 0

(aget rgb-array 2) ; yields 255

(alength rgb-array)

Efficient array creation:

; Creates an array with elements of type Object

(def rgb-array (to-array [0 0 255]))

; Creates an array with specified type

(def rgb-array (into-array Integer [0 0 255]))

Transforming with amap (creates a modified copy):

; amap's signature: (amap a idx ret expr)

; this returns an array with inverted color values

(amap rgb-array idx _ (- 255 (aget rgb-array idx)))

Traverse array with areduce:

(def numbers (into-array Integer [1 3 9 6 12 4]))

; areduce signature:

; (areduce a idx ret init expr)

; find max value

(areduce numbers idx ret (aget numbers 0) (max ret (aget numbers idx)))

; find min value

(areduce numbers idx ret (aget numbers 0) (min ret (aget numbers idx)))

Convenience functions

Format:

(format "The month %s has %d days." "May" 31)

Benchmarking:

(defn count-to [n]

        (loop [i 0]

                (if (< i n)

              (recur (inc i)))))

(dotimes [_ 5] (time (count-to 100000)))

Java methods vs Closure functions

Problem: Java methods can't always be treated as Clojure functions:

(map .toUpperCase ["a" "short" "message"])

; => java.lang.Exception:

;    Unable to resolve symbol: .toUpperCase in this context

Solution 1: memfn

(map (memfn toUpperCase) ["a" "short" "message"])

;           ^-- NO dot!

Solution 2: lambda (thumbs up)

(map (fn [s] (.toUpperCase s)) ["a" "short" "message"])

; shorter notation

(map #(.toUpperCase %) ["a" "short" "message"])

Introspection

Check if an object is an instance of a certain class:

(instance? Integer 10) ; yields true

(instance? String 10) ; yields false

(instance? Comparable 10) ; yields true

Show methods for a Java class:

; import the REPL utils first

(use 'clojure.contrib.repl-utils)

(show JFrame)

Show base classes of a Java class:

(ancestors JFrame)

Get Java class for a value:

(class 3)

(class "hello")

Java example

Create a Window and paint a blue rectangle:

(import javax.swing.JFrame)

(def f (new JFrame))

(.setSize f 200 200)

(.show f)

(def g (.getGraphics (.getContentPane f)))

(.setColor g (java.awt.Color/BLUE))

(.fillRect g 50 50 100 100)

Using the doto notation:

(import javax.swing.JFrame)

(def f (new JFrame))

(doto f (.setSize 200 200)

            (.show))

(def g (.getGraphics (.getContentPane f)))

(doto g (.setColor (java.awt.Color/BLUE))

            (.fillRect 0 0 200 200))

(.fillRect g 50 50 100 100)

Implementing Interfaces

Implementing Runnable:

; Create a proxy that implements the "run"

; method of the Runnable interface.

(.start

  (new Thread (proxy [Runnable] [] (run [] (println "Running!" )))))

; Shorter version: Clojure functions implement

; Runnable and Callable out of the box so we

; can leave out the the proxy.

(.start

  (new Thread (fn [] (println "Running!"))))

Exception handling

Safely writing to a file:

; write "hello" to "data.txt"

; calls w.close() in internally created finally block

(with-open [w (new java.io.PrintWriter "data.txt")]

  (.print w "hello"))

Try/catch/finally:

; try block

(try (println "Try"))

; try + finally

(try (println "Try") (finally (println "Cleanup")))

; throwing and catching exception

(try

  (println "Try")

  (throw (new Exception "Throwing an exception"))

  (println "Never called")

  (catch Exception exc

        (println "Caught exception:" (.getMessage exc)))

  (finally

        (println "Cleanup")))

Sequences

Constructing a sequence:

; from vector

(seq [1 2 3])

; from list

(seq '(1 2 3))

; from a map

(seq {1 "one" 2 "two" 3 "three"})

; from a set

(seq #{ :do :re :mi })

; from a range

(range 0 10) ; yields (0 1 2 3 4 5 6 7 8 9)

Basic seq operations:

; get first element

(first [1 2 3]) ; yields 1

(first {:a 1 :b 2 :c 3}) ; yields [:a 1]

(first #{"do" "re" "mi"}) ; yields "do"

; without first element

(rest [1 2 3]) ; yields (2 3)

; prepended

(cons 1 []) ; yields (1)

(cons 0 [1 2]) ; yields (0 1 2)

Adding elements:

; on a vector

(conj [] 1) ; yields [1]

(conj [1] 2) ; yields [1 2]

; on a list

(conj '(1 2) 3) ; yields (3 1 2)

; on a map

(conj {:a 1 :b 2} [:c 3])  

 

; on a set

(conj #{:do :re} :mi)

Combine collections:

(into [1 2] [3 4]) ; yields [1 2 3 4]

(into #{1 2} [3 4]) ; yields #{1 2 3 4}

(into [1 2] #{3 4}) ; yields [1 2 3 4]

(into [1 2] {:a 1 :b 2}) ; yields [1 2 [:a 1] [:b 2]]

; can't convert vector to map

(into {:a 1 :b2} [3 4]) ; java.lang.ArrayIndexOutOfBoundsException: 3

Create ranged sequence:

; range from 0 to 10

(range 0 10)

; shorter notation

(range 10)

; range with step 2

(range 0 10 2) ; yields (0 2 4 6 8)

Repeat:

(repeat 5 1) ; yields (1 1 1 1 1)

(repeat 3 :a) ; yields (:a :a :a)

(repeat 1) ; yields (1 1 1 ...)

(take 5 (repeat 1)) ; yields (1 1 1 1 1)

Iterate:

(iterate inc 1) ; yields (1 2 3 ...)

(take 10 (iterate inc 1)) ; yields (1 2 3 4 5 6 7 8 9 10)

Cycle:

(cycle [1 2 3]) ; yields (1 2 3 1 2 3 ...)

(take 4 (cycle [1 2 3])) ; yields (1 2 3 1)

(take 4 (cycle (range 3))) ; yields (0 1 2 0)

Interleave:

(interleave [:a :b :c] [1 2 3]) ; yields (:a 1 :b 2 :c 3)

(interleave (repeat "a") [1 2 3]) ; yields ("a" 1 "a" 2 "a" 3)

Introduce a list separator:

(interpose "," ["a" "b" "c"]) ; yields ("a" "," "b" "," "c")

(apply str (interpose ", " ["a" "b" "c"])) ; yields "a, b, c"

Filter

(filter even? '(1 2 3 4 5)) ; yields (2 4)

Take-while

(take-while (fn [x] (< x 4)) (iterate inc 1))

Replace

(replace { "old" "new" "hat" "shoes" } ["my" "old" "hat" ])

 -> ["my" "new" "shoes"]

Transforming sequences

Map:

(map #(.toUpperCase %) ["a", "b", "c"])

  -> ("A" "B" "C")

(map + [1 2 3] [1 2 3])

  -> (2 4 6)

Reduce:

(reduce + [1 2 3]) ; yields 6

(reduce * [1 2 3 4]) ; yields 24

Recursion

Regular recursion (slow and possible stack overflow):

; power function

(defn power [a n]

   (if (== n 1)

          a

          (* a (power a (dec n)))))

; examples

(power 2 3) ; yields 8

(power 4 2) ; yields 16

Tail-recursion using 'recur' (faster and no stack growth):

(defn power [a n]

  ; helper function that has tail-recursion

  (let [multiply (fn [x factor i]

                       (if (zero? i)

                         x

                         (recur (* x factor) factor (dec i))))]

  (multiply a a (dec n))))

; examples

(power 2 3) ; yields 8

(power 4 2) ; yields 16

Lazy sequence (also fast):

; create a sequence of (a a a ..)

(defn repeater [a]

  (lazy-seq

        (cons a (repeater a))))

; examples

(repeater 1) ; yields (1 1 ...)

(take 3 (repeater 2)) ; yields (2 2 2)

; power function using lazy sequence

(defn power [a n]

  (letfn [(multiply [a factor]

                (lazy-seq

                  (cons a (multiply (* a factor) factor))))]

  (nth (multiply a a) (dec n))))

; example

(power 2 3) ; yields 8

Trampoline (for unwinding mutual recursion):

; Regular mutual recursion

; ------------------------

(declare is-odd?)

(defn is-even? [n]

  (if (zero? n) true

                    (is-odd? (dec n))))

                   

(defn is-odd? [n]

  (if (zero? n) true

                    (is-even? (dec n))))

; Trampoline solution

; -------------------

(declare is-odd?)

; return functor instead of value

(defn is-even? [n]

  (if (zero? n) true

                    #(is-odd? (dec n))))

                   

; here too

(defn is-odd? [n]

  (if (zero? n) true

                    #(is-even? (dec n))))

; trampoline uses recur internally

(trampoline is-even? 100)

Memoization:

(defn fib [n]

  (if (== 0 n) 0

  (if (== 1 n) 1

  (+ (fib (- n 1)) (fib (- n 2))))))  

(def fib (memoize fib))

(fib 100) ; yields 354224848179261915075 instantly

Concurrency: references

Using alter on a reference:

; define i and j with value 0

(def i (ref 0))

(def j (ref 0))

; mutation must occur within a dosync block

(dosync

  (alter i inc)  ; increments i

  (alter j dec)) ; decrements j

(println @i) ; prints 1

(println @j) ; prints -1

Using commute on a reference:

; commute is similar to alter but it allows out-of-order changes

; so it should only be used if the changes are commutative

; dosync + commute

(dosync (commute i inc)) ; increment i

Validating references:

; define value i with value 0 and add a constraint

; that requires it to be a positive number

(def i (ref 0 :validator (fn [n] (>= 0 n))))

; try decrementing i

(dosync (alter i dec))

  -> java.lang.IllegalStateException: Invalid reference state

Concurrency: atoms

Atoms allow updates of a single value, uncoordinated with anything else.

Create an atom:

(def i (atom 0))

Getting the value of an atom:

; deref an atom

(deref i)

; shorter notation

@i

Changing the value of an atom:

; using a value

(reset! i 1)

; using a functor

(swap! i inc)

; use swap + assoc to change values in a map

(def point (atom { :x 0 :y 0 } ))

(swap! point assoc :x 1)

Concurrency: agents

Agents can be used if you want to execute a function in a separate thread.

Create an agent:

(def i (agent 0))

Get the value of an agent:

(deref i)

; shorter notation

@i

Start a new thread:

; non-blocking

(send i inc) ; increments i in a new thread

; blocking

(await (send i inc))

; blocking with timeout of 1000 ms

(await-for 1000 (send i inc))

Validating agents:

; i must be numeric

(def i (agent 0 :validator number?))

(send i (fn [_] "hello!"))

  -> java.lang.IllegalStateException: Invalid reference state

; agent is now in error state

@i

  -> java.lang.Exception: Agent has errors

; get error info

(agent-errors i)

; clear the error state

(clear-agent-errors i)

; you can now use the agent again

@i ; yields 0

Macros

Simplest example:

; create a "say-hello" macro

(defmacro say-hello [name] (println "Hello" name))

; invoke it

(say-hello "Tom")

Hello Tom

More advanced example:

; define an "unless" macro

(defmacro unless [expr body] (list 'if expr nil body))

; test it

(unless (= 1 2) (println "Yo"))

Yo

Using quote:

; "quote" is removed at macro expansion time

(quote (1 2 3))

=> (1 2 3)

; shorter notation

'(1 2 3)

=> (1 2 3)

; only one is quote is chopped of per macro expansion

(quote (quote (1 2 3)))

=> (quote (1 2 3)

Using macroexpand-1 and macroexpand

; use macroexpand-1 to resolve one level

(macroexpand-1 '(.. arm getHand getFinger))

=> (.. (. arm getHand) getFinger)

; use macroexpand to recursively resolve the entire macro

(macroexpand '(.. arm getHand getFinger))

=> (. (. arm getHand) getFinger)

Using synax quote (` and ~)

; syntax quotes enhance readability

(defmacro chain [x form] `(. ~x ~form))

; test it

(macroexpand '(chain person name))

=> (. person name)

; accept multiple arguments

(defmacro chain

  ([x form] `(. ~x ~form))

  ([x form & more] `(chain (. ~x ~form) ~@more))) ; @ is a splicer

; test it

(macroexpand '(chain a b c d))

=> (. (. (. a b) c) d)

; Syntax Quote rules

; ------------------

; 1. Begin the macro body with a syntax quote (‘)

         to treat the entire thing as a template.

; 2. Insert individual arguments with an unquote (~).

; 3. Splice in more arguments with splicing unquote (~@).

Using gensym

; generate a unique variable name

(gensym)

=> G__266

; generate a unique variable name with prefix

(gensym "test") 

=> test270

; Example

; -------

; sum3 is a function that adds three numbers:

(defn sum3 [a b c] (let [sum2 (fn [x y] (+ x y))] (sum2 (sum2 a b) c)))

(sum3 1 2 3) ; yields 6

; let's rewrite it as a macro

(defmacro sum3 [a b c] `(let [sum2 (fn [x y] (+ x y))] (sum2 (sum2 ~a ~b) ~c)))

(sum3 1 2 3)

=> java.lang.Exception: Can't let qualified name: user/sum2

; Solution

; --------

; Append # to local variables to guarantuee that

; a unique variable name will be generated so

; that the expanded macro will not conflict with

; its surrounding code.

(defmacro sum3 [a b c] `(let [sum2# (fn [x# y#] (+ x# y#))] (sum2# (sum2# ~a ~b) ~c)))

Creating vars dynamically

; normal way of creating a var

(def a 3)

; creating a var using a macro

(defmacro define-var [name value] `(def ~name ~value))

(define-var a 3)

Delayed evaluation

; function that gets timestamp of current time

(defn get-timestamp [] (.getTime (java.util.Date.)))

; define the var current-time and bind it to delayed get-timestamp

(def current-time (delay (get-timestamp)))

; force the evaluation

(force current-time)

; NOTE that the REPL sometimes forces evaluation

; even if you don't use the force call.

; See: Delayed evaluation in Clojure