francis.rammeloo@gmail.com
These are my notes while learning Clojure from the book Programming Clojure by Stuart Halloway.
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)
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))
(println "Hello world")
; this is a comment
(println "Hello world") ; prints hello world
(def age 29)
(def firstname "Francis")
(print age)
(age) ; shown in REPL only
(+ 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)
; convert fraction to int
(int (/ 3 2)) ; yields 1
; convert int to boolean
(boolean 1) ; yields true
(defn sum [a b] (+ a b))
(defn say-hello [name] (println "Hello" name))
(sum 3 4)
(say-hello "John")
(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
(fn [a b] (+ a b))
; example
(println ((fn [a b] (+ a b)) 3 4))
; Shorter notation
#(+ %1 %2)
; example
(println (#(+ %1 %2) 3 4))
(defn circle-area [radius]
(let [pi 3.141593
square (fn [x] (* x x ))]
(* pi (square radius))))
; example
(circle-area 2) ; yields 12.566372
(if (< 3 4) (println "true") (println "false"))
(if (> 3 4) (println "true") (println "false"))
(if (= 3 4) (println "true") (println "false"))
(if (identical? 3 4) (println "true") (println "false"))
(if (not (= 3 4)) (println "true") (println "false"))
(zero? 0) ; yields true
; 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))))
(defn sum-to [n]
(loop [i 1 sum 0]
(if (<= i n)
(recur (inc i) (+ i sum))
sum)))
(loop [] (recur))
; create list
(list 1 2 3)
; shorter notation
'(1 2 3)
(count '(1 2 3)) ; yields 3
; 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 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))
; create set
(set [:do :re :mi])
; shorter notation
#{:do :re :mi}
; see "Sequences" for examples on how to use sets
; 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)
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)
(in-ns 'playground)
(clojure.core/use 'clojure.core)
(use 'clojure.contrib.math)
(use :reload 'clojure.contrib.math)
(use :reload-all 'clojure.contrib.math)
References allow mutating state within a transaction.
(def i (ref 0))
(def text (ref "hello"))
; empty reference
(def i (ref ())
(println (deref i ))
(println (deref text))
; shorter notation
(println @i)
(println @text)
; 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))))
; 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))
(import java.util.Random)
; constructor
(new Random)
; shorter notation (syntactic sugar)
(Random.)
; constructor
(def rnd (new java.util.Random))
; Java: rnd.nextInt(10)
(. rnd nextInt 10)
; shorter notation (syntactic sugar)
(.nextInt rnd 10)
(import java.awt.Color)
; call Color.BLUE or Math.PI
(. Color BLUE)
(. Math PI)
; shorter notation (syntactic sugar)
(Color/BLUE)
(Math/PI)
(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)
; 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]))
; 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)))
(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)))
(format "The month %s has %d days." "May" 31)
(defn count-to [n]
(loop [i 0]
(if (< i n)
(recur (inc i)))))
(dotimes [_ 5] (time (count-to 100000)))
(map .toUpperCase ["a" "short" "message"])
; => java.lang.Exception:
; Unable to resolve symbol: .toUpperCase in this context
(map (memfn toUpperCase) ["a" "short" "message"])
; ^-- NO dot!
(map (fn [s] (.toUpperCase s)) ["a" "short" "message"])
; shorter notation
(map #(.toUpperCase %) ["a" "short" "message"])
(instance? Integer 10) ; yields true
(instance? String 10) ; yields false
(instance? Comparable 10) ; yields true
; import the REPL utils first
(use 'clojure.contrib.repl-utils)
(show JFrame)
(ancestors JFrame)
(class 3)
(class "hello")
(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)
(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)
; 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!"))))
; 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 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")))
; 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)
; 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)
; 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)
(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
; 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 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 inc 1) ; yields (1 2 3 ...)
(take 10 (iterate inc 1)) ; yields (1 2 3 4 5 6 7 8 9 10)
(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 [: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)
(interpose "," ["a" "b" "c"]) ; yields ("a" "," "b" "," "c")
(apply str (interpose ", " ["a" "b" "c"])) ; yields "a, b, c"
(filter even? '(1 2 3 4 5)) ; yields (2 4)
(take-while (fn [x] (< x 4)) (iterate inc 1))
(replace { "old" "new" "hat" "shoes" } ["my" "old" "hat" ])
-> ["my" "new" "shoes"]
(map #(.toUpperCase %) ["a", "b", "c"])
-> ("A" "B" "C")
(map + [1 2 3] [1 2 3])
-> (2 4 6)
(reduce + [1 2 3]) ; yields 6
(reduce * [1 2 3 4]) ; yields 24
; 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
(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
; 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
; 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
; 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
; 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
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)
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
; create a "say-hello" macro
(defmacro say-hello [name] (println "Hello" name))
; invoke it
(say-hello "Tom")
Hello Tom
; define an "unless" macro
(defmacro unless [expr body] (list 'if expr nil body))
; test it
(unless (= 1 2) (println "Yo"))
Yo
; "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)
; 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)
; 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 (~@).
; 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