Living Clojure
(reading notes)
- Chapter 1. The Structure of Clojure
- Chapter 2. Flow and Functional Transformations
- Chapter 3. State and Concurrency
- Chapter 4. Java Interop and Polymorphism
- Chapter 5. How to Use Clojure Projects and Libraries
- Chapter 6. Communication with core.async
- Chapter 7. Creating Web Applications with Clojure
- Chapter 8. The Power of Macros
- Chapter 9. Joining the Clojure Community
- Chapter 10. Weekly Living Clojure Training Plan
- Chapter 11. Further Adventures
Preparation
- install java
- install Leiningen
- create a new project
lein new wonderland
- entering REPL:
cd wonderland; lein repl
Chapter 1. The Structure of Clojure
simple values or literals simply evaluate to themselves, for example:
- integer:
12
- decimal:
12.43
- ratio:
1/3
- character:
\j
- keyword:
:jam
- string:
"jam"
- boolean:
true
,false
- null:
nil
expressions
(+ 1 (+ 8 3))
collections
- lists
- vectors
- maps
- sets
all collections are immutable and persistent.
Immutable means the value of the collection does not change. A function to change a collection gives you back a new version of the collection.
Persistent means collections will do smart creations of new versions of themselves by using structural sharing.
Lists
Lists are collections of data what you want to access from the top of the list.
to create a list, simply put a quote in front of the parents:
'(1 2 "jam" :marmalade-jar)
you can mix and match values in a collections.
this is also valid:
'(1, 2, "jam", :bee)
commas are ignored and treated like whitespace. (but just use spaces)
some basic functions:
(first '(:rabbit :pocket-watch :marmalade :door))
;; -> :rabbit
(rest '(:rabbit :pocket-watch :marmalade :door))
;; -> (:pocket-watch :marmalade :door)
(cons 5 '()) ;; same as (cons 5 nil)
;; -> (5)
(cons 4 (cons 5 nil))
;; -> (4 5)
'(1 2 3 4 5)
;; -> (1 2 3 4 5)
(list 1 2 3 4 5)
;; -> (1 2 3 4 5)
Vectors
Vectors are for collections of data that you want to access anywhere by position.
to create vector, use square brackets
[:jar1 1 2 3 :jar2]
(first [:jar1 1 2 3 :jar2])
;; -> :jar1
(rest [:jar1 1 2 3 :jar2])
;; -> (1 2 3 :jar2)
;; return a list
in vectors, you have fast index access to the elements:
(nth [:jar1 1 2 3 :jar2] 0)
;; -> :jar1
(nth [:jar1 1 2 3 :jar2] 2)
;; -> 2
(last [:rabbit :pocket-watch :marmalade])
;; -> :marmalade
(last (:rabbit :pocket-watch :marmalade))
;; -> :marmalade
vectors have better index access performance than lists, if you need to access the elements of collection by index, use a vector.
more common functions
(count [1 2 3 4])
;; -> 4
(conj [:toast :butter] :jam :honey)
;; -> [:toast :butter :jam :honey]
(conj '(:toast :butter) :jam :honey)
;; -> (:honey :jam :toast :butter)
conj
adds to a collection in the most natural way for the data structure. for lists, it adds to the beginning. for vectors, it adds to the end.
Maps
Maps are for key-value pairs.
to create maps, use curly braces:
{:jam1 "strawberry" :jam2 "blackberry"}
maps are the one place that it can be idiomatic to leave the commas in for readability:
{:jam1 "strawberry", :jam2 "blackberry"}
get a value from maps
(get {:jam1 "strawberry", :jam2 "blackberry"} :jam2)
;; -> "blackberry"
;; get with a default value, put it as the last argument
(get {:jam1 "strawberry", :jam2 "blackberry"} :jam3 "not found")
;; -> "not found"
a more idiomatic way to get value from maps:
(:jam2 {:jam1 "strawberry" :jam2 "blackberry" :jam3 "marmalade"})
;; -> "blackberry"
when using keyword as key, the key itself is also a function.
get keys and values:
(keys {:jam1 "strawberry" :jam2 "blackberry" :jam3 "marmalade"})
;; -> (:jam3 :jam2 :jam1)
(vals {:jam1 "strawberry" :jam2 "blackberry" :jam3 "marmalade"})
;; -> ("marmalade" "blackberry" "strawberry")
(remember, collections are immutable, below you actually get a new version of collection)
to update value:
(assoc {:jam1 "red" :jam2 "black"} :jam1 "orange")
;; -> {:jam2 "black", :jam1 "orange"}
to remove value:
(dissoc {:jam1 "strawberry" :jam2 "blackberry"} :jam1)
;; -> {:jam2 "blackberry"}
merge:
(merge {:jam1 "red" :jam2 "black"}
{:jam1 "orange" :jam3 "red"}
{:jam4 "blue"})
;; -> {:jam4 "blue", :jam3 "red", :jam2 "black", :jam1 "orange"}
Sets
Sets are for collections of unique elements.
create a set with #{}
#{:red :blue :white :pink}
;; duplicate are not allowed
#{:red :blue :white :pink :pink}
;; -> error
common sets functions in clojure.set
:
;; union
;; combine all
(clojure.set/union #{:r :b :w} #{:w :p :y})
;; -> #{:y :r :w :b :p}
;; difference
;; takes elements away from one of the sets
(clojure.set/difference #{:r :b :w} #{:w :p :y})
;; -> #{:r :b}
;; intersection
;; return only shared elements
(clojure.set/intersection #{:r :b :w} #{:w :p :y})
;; -> #{:w}
convert other collecions to sets:
(set [:rabbit :rabbit :watch :door])
;; -> #{:door :watch :rabbit}
(set {:a 1 :b 2 :c 3})
;; -> #{[:c 3] [:b 2] [:a 1]}
get element from a set:
(get #{:rabbit :door :watch} :rabbit)
;; -> :rabbit
(get #{:rabbit :door :watch} :jar)
;; -> nil
;; use keyword as a function
(:rabbit #{:rabbit :door :watch})
;; -> :rabbit
;; the set itself can be used as a function to do the same thing
(#{:rabbit :door :watch} :rabbit)
;; -> :rabbit
check if element exists:
(contains? #{:rabbit :door :watch} :rabbit)
;; -> true
(contains? #{:rabbit :door :watch} :jam)
;; -> false
add element to a set:
(conj #{:rabbit :door} :jam)
;; -> #{:door :rabbit :jam}
remove element from a set:
(disj #{:rabbit :door} :door)
;; -> #{:rabbit}
Symbols
Clojure symbols refer to values. when a symbol is evaluated, it returns the thing it refer to.
def
gives something a name, so we can refer to it.
(def developer "Alice")
;; -> #'user/developer
def
created a var object in the default namespace (user
) for the symbol developer
.
developer
;; -> "Alice"
user/developer
;; -> "Alice"
when you want to have a temporary var, use let
.
let
allows us to have bindings to symbols that are only available within the context of the let
.
What happens in a let
, stays in the let
.
(def developer "Alice")
;; -> #'user/developer
(let [developer "Alice in Wonderland"]
developer)
;; -> "Alice in Wonderland"
the bindings of let
are in a vector form. It expects pairs of symbol and values.
(let [developer "Alice in Wonderland"
rabbit "White Rabbit"]
[developer rabbit])
;; -> ["Alice in Wonderland" "White Rabbit"]
- use
def
to create global vars. - use
let
to create temporary bindings.
Creating a function
defn
takes following arguments:
- name of the function
- a vector of parameters
- body of the function
(defn follow-the-rabbit [] "Off we go!")
;; -> #'user/follow-the-rabbit
(follow-the-rabbit)
;; -> "Off we go!"
(defn shop-for-jam [jam1 jam2]
{:name "jam-basket"
:jam1 jam1
:jam2 jam2})
;; -> #'user/shop-for-jam
(shop-for-jam "strawberry" "marmalade")
;; -> {:name "jam-basket", :jam1 "strawberry", :jam2 "marmalade"}
anonymous functions
;; return a function
(fn [] (str "Off we go" "!"))
;; invoke it directly
((fn [] (str "Off we go" "!")))
;; -> "Off we go!"
;; shorthand, use # in front of the parens:
(#(str "Off we go" "!"))
;; -> "Off we go!"
;; for parameters
;; if only 1 parameter, just use %
(#(str "Off we go" "!" " - " %) "again")
;; -> "Off we go! - again"
;; more than 1, just %1, %2 ...
(#(str "Off we go" "!" " - " %1 %2) "again" "?")
;; -> "Off we go! - again?"
defn
is just the same as using def
and binding the name to the anonymous function:
(def follow-again (fn [] (str "Off we go" "!")))
;; -> #'user/follow-again
(follow-again)
;; -> "Off we go!"
Namespaces
create a new namespace:
(ns alice.favfoods)
check current namespace:
*ns*
3 main ways of using libs in your namespace using require:
- use
require
expression with the namespace as argument.
(require 'clojure.set)
- use
require
expression with an alias using:as
.
(require '[alice.favfoods :as af])
;; it's also common to see it nested within the ns:
(ns wonderland
(:require [alice.favfoods :as af]))
- use
require
with the namespace and the:refer :all
options.
(ns wonderland
(:require [alice.favfoods :refer :all]))
however, the last way may create conflicts and also hard to read the code.
there's also a use
expression that is the same as the require
with :refer :all
.
sum up
(ns wonderland
(:require [clojure.set :as s]))
(defn common-fav-foods [foods1 foods2]
(let [food-set1 (set foods1)
food-set2 (set foods2)
common-foods (s/intersection food-set1 food-set2)]
(str "Common Foods: " common-foods)))
(common-fav-foods [:jam :brownies :toast]
[:lettuce :carrots :jam])
;; -> "Common Foods: #{:jam}"
Chapter 2. Flow and Functional Transformations
expression and form
expression is code that can be evaluated for a result.
form is term for a valid expression that can be evaluated.
(first)
is an expression but not a valid form.
Controlling the Flow with Logic
(class true)
;; -> java.lang.Boolean
(true? true)
;; -> true
(true? false)
;; -> false
(false? false)
;; -> true
(false? true)
;; -> false
(nil? nil)
;; -> true
(nil? 1)
;; -> false
(not true)
;; -> false
(not false)
;; -> true
(not nil)
;; -> true
remember, nil
is logically false in tests.
(= :drinkme :drinkme)
;; -> true
;; collection equality is special:
(= '(:drinkme :bottle) [:drinkme :bottle])
;; -> true
;; not= is a shortcut for (not (= x y))
(not= :drinkme :4)
;; -> true
Logic Tests on Collections
(empty? [:table :door :key])
;; -> false
(empty? [])
;; -> true
(empty? {})
;; -> true
(empty? '())
;; -> true
if we look at the definition of empty?
, there is seq
:
(defn empty?
[coll] (not (seq coll)))
In Clojure, there are the collection and sequence abstractions.
The collections are simply a collection of elements.
The seq
function turns the collection into a sequence.
A sequence is a walkable list abstraction for the collection data structure.
(empty? [])
;; -> true
(seq [])
;; -> nil
remember, use seq
to check for not empty instead of (not (empty? x))
. this is because nil
is treated as logically false in tests.
;; every? takes a predicate and a collection as arguments
(every? odd? [1 3 5])
;; -> true
(every? odd? [1 2 3 4 5])
;; -> false
A predicate is a function that returns a value used in a logic test.
create a predicate:
(defn drinkable? [x]
(= x :drinkme))
(every? drinkable? [:drinkme :drinkme])
;; -> true
;; use anonymous function
(every? (fn [x] (= x :drinkme)) [:drinkme :drinkme])
;; -> true
;; or
(every? #(= % :drinkme) [:drinkme :drinkme])
;; -> true
other logical test functions:
(not-any? #(= % :drinkme) [:drinkme :poison])
;; -> false
(not-any? #(= % :drinkme) [:poison :poison])
;; -> true
(some #(> % 3) [1 2 3 4 5])
;; -> true
some
function can be used with a set to return the element, or the first matching element of the sequence.
;; set is a function of its own member
(#{1 2 3 4 5} 3)
;; -> 3
(some #{3} [1 2 3 4 5])
;; -> 3
(some #{4 5} [1 2 3 4 5])
;; -> 4
Harnessing the Power of Flow Control
if
takes 3 parameters
- the expression that is the logical test.
- (if the test expression evaluates to true), evaluates the 2nd parameter.
- (if false), evaluates the 3rd parameter.
(if true "it is true" "it is false")
;; -> "it is true"
(if false "it is true" "it is false")
;; -> "it is false"
(if nil "it is true" "it is false")
;; -> "it is false"
(if (= :drinkme :drinkme)
"Try it"
"Don't try it")
;; -> "Try it"
combining let
and if
(let [need-to-grow-small (> 5 3)]
(if need-to-grow-small
"drink bottle"
"don't drink bottle"))
;; -> "drink bottle"
or just use if-let
:
(if-let [need-to-grow-small (> 5 1)]
"drink bottle"
"don't drink bottle"))
;; -> "drink bottle"
if you only want to do one thing when test is true, use when
when
takes a predicate, if it is logical true, it will evaluate the body. otherwise, it returns nil
.
(defn drink [need-to-grow-small]
(when need-to-grow-small "drink bottle"))
(drink true)
;; -> "drink bottle"
(drink false)
;; -> nil
there is also a when-let
:
(when-let [need-to-grow-small true]
("drink bottle")
;; -> "drink bottle"
(when-let [need-to-grow-small false]
("drink bottle")
;; -> nil
when we want to test for mutiple things, use cond
:
(let [bottle "drinkme"]
(cond
(= bottle "poison") "don't touch"
(= bottle "drinkme") "grow smaller"
(= bottle "empty") "all gone"))
;; -> "grow smaller"
in the cond
clause, once a logical test returns true and the expression is evaluated, none of the other test clauses are tried.
(let [x 5]
(cond
(> x 10) "bigger than 10"
(> x 4) "bigger than 4"
(> x 3) "bigger than 3"))
;; -> "bigger than 4"
for returning a default value instead of nil
, use :else
(let [bottle "mystery"]
(cond
(= bottle "poison") "don't touch"
(= bottle "drinkme") "grow smaller"
(= bottle "empty") "all gone"
:else "unknown"))
;; -> "unknown"
case
is shortcut for the cond
where there is only one test value and it can be compared with an =
:
(let [bottle "drinkme"]
(case bottle
"poison" "don't touch"
"drinkme" "grow smaller"
"empty" "all gone"))
;; -> "grow smaller"
however, case
will throw an exception for no matching test. simply add an extra value as default:
(let [bottle "mystery"]
(case bottle
"poison" "don't touch"
"drinkme" "grow smaller"
"empty" "all gone"
"unknown"))
;; -> "unknown"
Functions Creating Functions and Other Neat Expressions
partial is a way of currying in Clojure. Currying is a way to generate a new function with an argument partially applied.
(defn adder [x y]
(+ x y ))
(adder 3 4)
;; -> 7
(defn adder-5 (partical adder 5))
(adder-5 10)
;; -> 15
comp creates a new function that combines other functions. It takes any number of functions as its parameters and returns the composition of those functions going from right to left.
(defn toggle-grow [direction]
(if (= direction :small) :big :small))
(defn oh-my [direction]
(str "Oh My! You are growing " direction))
(oh-my (toggle-grow :small))
;; -> "Oh My! You are growing :big"
;; or use comp
(defn surprise [direction]
((comp oh-my toggle-grow) direction))
Destructuring
destructuring allows you to assign named bindings for the elements in things like vectors and maps.
(let [[color size] ["blue" "small"]]
(str "The " color " door is " size))
;; -> "The blue door is small"
the destructuring knew what values to bind by the placement of the symbols in the binding expression.
;; without destructuring:
(let [x ["blue" "small"]
color (first x)
size (last x)]
(str "The " color " door is " size))
;; -> "The blue door is small"
to keep th initial data structure as a binding, use :as
keyword:
(let [[color [size] :as original] ["blue" ["small"]]]
{:color color :size size :original original})
;; -> {:color "blue", :size "small", ":original ["blue" ["small"]]}
destructuring can also be done with maps:
(let [{flower1 :flower1 flower2 :flower2}
{:flower1 "red" :flower2 "blue"}]
(str "The flowers are " flower1 " and " flower2))
;; -> "The flowers are red and blue"
specify default values with :or
:
(let [{flower1 :flower1 flower2 :flower2 :or {flower2 "missing"}}
{:flower1 "red"}]
(str "The flowers are " flower1 " and " flower2))
;; -> "The flowers are red and missing"
:as
works in maps too:
(let [{flower1 :flower1 :as all-flowers}
{:flower1 "red"}]
[flower1 all-flowers])
;; -> ["red" {:flower1 "red"}]
most of the time, you will want to give the same name to the binding as the name of the key, use :keys
directive:
(let [{:keys [flower1 flower2]}
{:flower1 "red" :flower2 "blue"}]
(str "The flowers are " flower1 " and " flower2))
;; -> "The flowers are red and blue"
destructuring is also available to use on parameters while defining functions with defn
:
(defn flower-colors [{:keys [flower1 flower2]}]
(str "The flowers are " flower1 " and " flower2))
(flower-colors {:flower1 "red" :flower2 "blue"})
;; "The flowers are red and blue"))
The Power of Laziness
Clojure can also work with infinite lists.
(take 5 (range))
;; -> (0 1 2 3 4)
calling range
returns a lazy sequence, you an specify an end for the range by passing it a parameter:
(range 5)
;; -> (0 1 2 3 4)
(class (range 5))
;; -> clojure.lang.LazySeq
but when you don't specify an end, the default is infinity.
repeat
can be used to generate an infinite sequence of repeated items:
(repeat 3 "rabbit")
;; -> ("rabbit" "rabbit" "rabbit")
(class (repeat 3 "rabbit"))
;; -> clojure.lang.LazySeq
repeatedly
takes a function that will be repeatedly executed over and over again.
(repeat 5 (rand-int 10))
;; -> (7 7 7 7 7)
(repeatedly 5 #(rand-int 10))
;; -> (1 5 8 4 3)
(take 10 (repeatedly #(rand-int 10)))
cycle
takes a collection as an argument and returns a lazy sequence of the items in the collection repeated infinitely.
(take 3 (cycle ["big" "small"]))
;; -> ("big" "small" "big")
rest
will return a lazy sequence when it operates on a lazy sequence:
(take 3 (rest (cycle ["big" "small"])))
;; -> ("small" "big" "small")
Recursion
(def adjs ["normal"
"too small"
"too big"
"is swimming"])
(def alice-is [in out]
(if (empty? in)
out
(alice-is
(rest in)
(conj out (str "Alice is " (first in))))))
(alice-is adjs [])
you can also do it with loop
(defn alice-is [input]
(loop [in input
out []]
(if (empty? in)
out
(recur (rest in)
(conj out (str "Alice is " (first in)))))))
(alice-is adjs)
recur
jumps back to the recursion point, which is the beginning of the loop, and rebinds with new parameters.
using recur
also has another very important advantage. It provides a way of not "consuming the stack" for recursive calls:
(defn countdown [n]
(if (= n 0)
n
(countdown (- n 1))))
(countdown 3)
;; -> 0
(countdown 100000)
;; -> StackOverflowError
in the recursive call, a new frame was added to the stack for every function call, recur
only needs one stack at a time.
(defn countdown [n]
(if (= n 0)
n
(recur (- n 1))))
(countdown 100000)
;; -> 0
In general, always use recur
when you are doing recursive calls.
The Functional Shape of Data Transformations
map
(def animals [:mouse :duck :dodo :lory :eaglet])
(map #(str %) animals)
;; -> (":mouse" ":duck" ":dodo" ":lory" ":eaglet")
(class (map #(str %) animals))
;; -> clojure.lang.LazySeq
map
returns a lazy sequence.
doall
forces evaluation of the side effets:
(def animal-print (doall (map #(println %) animals)))
;; -> do the println
(animal-print)
;; return value only but no println
map
can also take more than one collection to map against. It uses each collection as a parameter to the function.
the map
function will terminate when the shortest collection ends. because of this, we can even use an infinite list with it:
(def animals ["mouse" "duck" "dodo" "lory" "eaglet"])
(map gen-animal-string animals (cycle ["brown" "black"]))
reduce
differs from map
in that you can change the shape of the result.
(reduce + [1 2 3 4 5])
;; -> 15
(reduce (fn [r x] (+ r (* x x))) [1 2 3])
;; -> 14
unlike map
, you cannot reduce
an infinite sequence (e.g., range
)
filter
takes a predicate and a collection as an argument.
(filter (complement nil?) [:mouse nil :duck nil])
;; -> (:mouse :duck)
complement
function takes the function and returns a function that takes the same arguments, but returns the opposite truth value.
((complement nil?) nil)
;; -> false
remove
takes a predicate and a collection.
(remove nil? [:mouse nil :duck nil])
;; -> (:mouse :duck)
for
(for [animal [:mouse :duck :lory]]
(str (name animal)))
;; -> ("mouse" "duck" "lory")
the result is a lazy sequence.
if more than one collection is specified in the for
, it will iterate over them in a nested fashion.
(for [animal [:mouse :duck :lory]
color [:red :blue]]
(str (name color) (name animal)))
:let
modifier:
(for [animal [:mouse :duck :lory]
color [:red :blue]
:let [animal-str (str "animal-" (name animal))
color-str (str "color-" (name color))
display-str (str animal-str color-str)]]
display-str)
:when
modifier:
(for [animal [:mouse :duck :lory]
color [:red :blue]
:let [animal-str (str "animal-" (name animal))
color-str (str "color-" (name color))
display-str (str animal-str color-str)]
:when (= color :blue)]
display-str)
flatten
takes any nested collection and returns the contents in a single flattened sequence:
(flatten [[:duck [:mouse] [[:lory]]]])
;; -> (:duck :mouse :lory)
into
takes the new collection and returns all the items of the collection conj
-ed on to it:
(into [] '(1 2 3))
;; -> [1 2 3]
(into (sorted-map) {:b 2 :a 1 :z3})
// vectors into maps
(into {} [[:a 1] [:b 2] [:c 3]])
// maps into vectors
(into [] {:a 1, :b 2, :c 3})
partition
partition
is useful for dividing up collections:
(partition 3 [1 2 3 4 5 6 7 8 9 10])
;; -> ((1 2 3) (4 5 6) (7 8 9))
(partition-all 3 [1 2 3 4 5 6 7 8 9 10])
;; -> ((1 2 3) (4 5 6) (7 8 9) (10))
partition-by
takes a function and applies it to every element in the collection. it creates a new partition every time the result changes:
(partition-by #(= 6 %) [1 2 3 4 5 6 7 8 9 10])
;; -> ((1 2 3 4 5) (6) (7 8 9 10))
Chapter 3. State and Concurrency
Using Atoms for independent Items
Atoms are designed to store the state of something that is independent, meaning we can change the value of it independently of changing any other state.
(def who-atom (atom :caterpillar))
to see the value of the atom, need to dereference it with a preceding @
:
who-atom
;; -> #<Atom@....: :caterpillar>
@who-atom
;; -> :caterpillar
changes to atom are always made synchronously.
first is using reset!
, replaces old value with new value and returns the new value:
(reset! who-atom :chrysalis)
@who-atom
;; -> :chrysalis
the other way is with swap!
, applies a function on the old value and sets it to the new value:
(def change [state]
(case state
:caterpillar :chrysalis
:chrysalis :butterfly
:butterfly))
(swap! who-atom change)
@who-atom
;; -> :chrysalis
(swap! who-atom change)
@who-atom
;; -> :butterfly
when using swap!
, the function used must be free of side effects.
swap!
operator reads the value of the atom and applies the function on it, then compares the current value of the atom again to make sure that another thread hasn't changed it. if the value has changed in the meantime, it will retry.
this means any side effects in functions might be executed multiple times.
(def counter (atom 0))
@counter
;; -> 0
;; the understore `_` is the name of the value, we don't care here.
(dotimes [_ 5] (swap! counter inc))
@counter
;; -> 5
to use multiple threads on this, use the future
form. the future
form takes a body and executes it in another thread.
(let [n 5]
(future (dotimes [_ n] (swap! counter inc)))
(future (dotimes [_ n] (swap! counter inc)))
(future (dotimes [_ n] (swap! counter inc))))
@counter
;; -> 15
if we use a function with side effect:
(defn inc-print [val]
(println val)
(inc val))
(let [n 2]
(future (dotimes [_ n] (swap! counter inc-print)))
(future (dotimes [_ n] (swap! counter inc-print)))
(future (dotimes [_ n] (swap! counter inc-print)))
you can see some values printed multiple times.
Using Refs for Coordinated Changes
What if we have more than one thing that needs to change in a coordinated fashion? refs
allows coordinated shared state.
that makes them different from atoms is that you need to change their values within a transaction. Clojure uses software transactional memory (STM) to accomplish this.
All actions on refs within the transaction are:
- Atomic: updates will occur to all the refs, if something goes wrong, none of them will be updated.
- Consistent: optional validator function can be used to check value before the transaction commits.
- Isolated: transaction has its own isolated view of the world, it will not see any effects from other transactions.
(def alice-height (ref 3))
(def right-hand-bites (ref 10))
like atoms, use a preceding @
to dereference refs:
@alice-height
;; -> 3
alter
form takes a ref and a function to apply to the current value (similar to swap!
)
(defn eat-from-right-hand []
(when (pos? @right-hand-bites)
(alter right-hand-bites dec)
(alter alice-height #(+ % 24))))
(eat-from-right-hand)
;; -> IllegalStateException No transaction running
we need to run this in a transaction, we do this by using a dosync
form. it will coordinate any state changes within the form in a transaction.
(dosync (eat-from-right-hand))
;; -> 27
@alice-height
;; -> 27
@right-hand-bites
;; -> 9
try it with multiple threads:
(let [n 2]
(future (dotimes [_ n] (eat-from-right-hand)))
(future (dotimes [_ n] (eat-from-right-hand)))
(future (dotimes [_ n] (eat-from-right-hand))))
@alice-height
;; -> 147
@right-hand-bites
;; -> 4
the function of the alter
must be side-effect free too, the reason is the same: there would be retries.
there is another function called commute
that we could use instead of alter
, it also must be called in a transaction.
the difference between them is commute
will not retry during the transaction. instead, it will use an in-transaction-value in the meantime, finally setting the ref
value at the commit point in the transaction.
this feature is very nice for speed and limiting retries.
on the other hand, the function that commute
applied must be commutative (execute order does not matter, like addition), or have a last-one-in-wins behavior.
the example above can use commute
instead of alter
.
Transactions that involve time-consuming computations and a large number of refs are most likely to be retried. If you are looking to limit retries, this is a reason you might prefer an atom with a map of state over many refs.
when you have one ref what is defined in relation to another ref, use ref-set
instead of alter
:
(def x (ref 1))
(def y (ref 1))
(defn new-values []
(dosync
(alter x inc)
(ref-set y (+ 2 @x)))) ;; use ref-set
(let [n 2]
(future (dotime [_ n] (new-values)))
(future (dotime [_ n] (new-values))))
@x
;; -> 5
@y
;; -> 7
Using Agents to Manage Changes on Their Own
atoms and refs are synchronous, agents are used for independent and asynchronous changes.
if you don't need the result right away, you can pass it to an agent for processing.
(def who-agent (agent :caterpillar))
@who-agent
;; -> caterpillar
we can change the state of an agent by using send
.
(def change [state]
(case state
:caterpillar :chrysalis
:chrysalis :butterfly
:butterfly))
(send who-agent change)
@who-agent
;; -> :chrysalis
unlike swap!
and alter
, send
returns immediately.
there is another way to dispatch an action to the agent, with send-off
. the difference is send-off
should be used for potentially I/O-blocking actions.
send
uses a fixed thread pool, good for CPU-bound operations. send-off
uses an expandable thread pool necessary to avoid an I/O-bound thread pool from blocking:
agents can also handle transactions with their action, means we could change refs within an agent action, or send actions only if the transaction commits.
when there's an Exception:
(def who-agent (agent :caterpillar))
(defn change [state]
(case state
:caterpillar :chrysalis
:chrysalis :butterfly
:butterfly))
(defn change-error [state]
(throw (Exception. "Boom!")))
(send who-agent change-error)
;; -> failed
@who-agent
;; -> :caterpillar
the agent's state did not change. the agent itself is now failed.
The exception has been cached, and next time an action is processed, the agent's error will be thrown:
(send-off who-agent change)
;; -> Exception Boom!
you can inspect agent's erorr with agent-errors
:
(agent-errors who-agent)
;; -> Exception Boom!
the agent will stay in this failed state until the agent is restarted with restart-agent
, which clears its errors and resets the state of the agent:
(restart-agent who-agent :caterpillar)
;; -> :caterpillar
(send who-agent change)
@who-agent
;; -> :chrysalis
to control how the agent responds to errors, use set-error-mode!
, it can be set to either :fail
or :continue
:
(set-error-mode! who-agent :continue)
If it is set to :continue
and we assign an error handler with the set-error-handler-fn!
function, the error handler will happen on an agent exception, but the agent itself will continue on without a need for a restart:
(defn err-handler-fn [a ex]
(println "error " ex " value is " @a))
(set-error-mode! who-agent :continue)
(set-error-handler! who-agent err-handler-fn)
(send who-agent change-error)
;; -> print out
@who-agent
;; -> :caterpillar
;; however the agent will continue on without a restart for the next call
(send who-agent change)
@who-agent
;; -> :chrysalis
to sum up:
| Type | Communication | Coordination |
|-
| Atom | Synchronous | Uncoordinated |
| Ref | Synchronous | Coordinated |
| Agent | Asynchronous | Uncoordinated |
Chapter 4. Java Interop and Polymorphism
Clojure uses the new
and .
special form to interact with Java classes.
String cString = new String("caterpillar");
cString.toUpperCase();
clojure's equivalent:
(. "caterpillar" toUpperCase)
or
(.toUpperCase "caterpillar")
if the jave method takes arguments, they are included after the object.
String c1String = new String("caterpillar");
String c2String = new String("pillar");
c1String.indexOf(c2);
clojure's equivalent:
(.indexOf "caterpillar" "pillar")
in clojure, the first argument is the string we want to call the method on, and the second is the argument.
use new
to create instance of Java object:
(new String "Hi!!")
Another way to create an instance of a Java class is to use a shorthand form by using a dot right after the class name:
(String. "Hi!!")
to import Java classes, do it by using :import
in the namespace form:
(ns caterpillar.network
(:import (java.net InetAddress)))
to execute static methods on java class, use a forward slash (/
):
(InetAddress/getByName "localhost")
to get a property off of an object, use the dot notation:
(.getHostName (InetAddress/getByName "localhost"))
we can also use full qualified names without importing:
(java.net.InetAddress/getByName "localhost")
there is also a doto
macro, which allows us to take a java object and then act on it with a list of operations:
(def sb (doto (StringBuffer. "Who ")
(.append "are ")
(.append "you?")))
(.toString sb)
;; -> "Who are you?"
without doto
:
(def sb
(.append
(.append
(StringBuffer. "Who ")
"are ")
"you?"))
Practical Polymorphism
(defn who-are-you [input]
(cond
(= java.lang.String (class input)) "String - Who are you?"
(= clojure.lang.Keyword (class input)) "Keyword - Who are you?"
(= java.lang.Long (class input)) "Number - Who are you?"))
(who-are-you :alice)
;; -> Keyword
(who-are-you "alice")
;; -> String
(who-are-you 123)
;; -> Number
(who-are-you true)
;; -> nil
we can express this with polymorphism in clojure with multimethods.
first specifies how it is going to dispatch. that is, how it is going to decide which of the following methods to use:
(defmulti who-are-you class)
(defmethod who-are-you java.lang.Keyword [input]
(str "String - Who are you? " input))
(defmethod who-are-you clojure.lang.Keyword [input]
(str "Keyword - Who are you? " input))
(defmethod who-are-you java.lang.Long [input]
(str "Number - Who are you? " input))
(who-are-you :alice)
;; -> Keyword
(who-are-you "alice")
;; -> String
(who-are-you 123)
;; -> Number
(who-are-you true)
;; Exception
we can also provide a default dispatch method using the :default
keyword:
(defmethod who-are-you :default [input]
(str "I don't know - who are you?" input))
(who-are-you true)
;; -> I don't know ...
any function can be given to dispatch on. we can even inspect the value of a map as input:
(defmulti eat-mushroom (fn [height]
(if (< height 3)
:grow
:shrink)))
(defmethod eat-mushroom :grow [_]
"Eat the right side to grow.")
(defmethod eat-mushroom :shrink [_]
"Eat the left side to shrink.")
(eat-mushroom 1)
;; -> ... grow
(eat-mushroom 9)
;; -> ... shrink
Another way to use polymorphism in clojure is to use protocols.
protocols can handle polymorphism elegantly with groups of functions.
(defprotocol BigMushroom
(eat-mushroom [this]))
the parameter this
is the thing that we are going to perform the function on:
(extend-protocol BigMushroom
java.lang.String
(eat-mushroom [this]
(str (.toUpperCase this) " mmm tasty!"))
clojure.lang.Keyword
(eat-mushroom [this]
(case this
:grow "Eat the right side!"
:shrink "Eat the left side!"))
java.lang.Long
(eat-mushroom [this]
(if (< this 3)
:grow "Eat the right side to grow!"
:shrink "Eat the left side to shrink!")))
(eat-mushroom "Big Mushroom")
;; -> "BIG MUSHROOM mmm tasty!"
(eat-mushroom :grow)
;; -> "Eat the right side!"
(eat-mushroom 1)
;; -> "Eat the right side to grow!"
we are using protocols to add methods to existing data structure. what if we want to add our own data structure?
Clojure's answer to this is data types.
there're two solutions:
if you need structured data, use defrecord
, which creates a class with a new type.
if you just need an object with a type to save memory, use deftype
.
defrecord
the defrecord
form defineds the fields that the class will hold:
(defrecord Mushroom [color height])
;; -> caterpillar.network.Mushroom
now can create new object with a dot notation:
(def regular-mushroom (Mushroom. "white and blue polka dots" "2 inches"))
(class regular-mushroom)
;; -> caterpillar.network.Mushroom
we can get the values with the dot-dash that is preferred over the dot-prefix for accessing fields:
(.-color regular-mushroom)
;; -> "white ..."
(.-height regular-mushroom)
;; -> "2 inches"
we can combine structured data type with protocols to implement interfaces:
(defprotocol Edible
(bite-right-side [this])
(bite-left-side [this]))
implement the protocol:
(defrecord WonderlandMushroom [color height]
Edible
(bite-right-side [this]
(str "The " color " bite makes you grow bigger"))
(bite-left-side [this]
(str "The " color " bite makes you grow smaller")))
then another data type implements Edible
:
(defrecord RegularMushroom [color height]
Edible
(bite-right-side [this]
(str "The " color " bite tastes bad"))
(bite-left-side [this]
(str "The " color " bite tastes bad too")))
construct our mushroom objects:
(def alice-mushroom (WonderlandMushroom. "blue dots" "3 inches"))
(def reg-mushroom (RegularMushroom. "brown" "1 inch"))
(bite-right-side alice-mushroom)
;; -> "The blue dots bite makes you grow bigger"
(bite-right-side reg-mushroom)
;; -> "The brown bite tastes bad"
A real-world example of protocols is implementing different types of persistence. which is one data type writes to different types of data sources, one to database and one to s3 bucket for example.
deftype
sometimes we don't really care about the structure or the map lookup features provided by defrecord
, we just need an object with a type to save memory.
in this case we should reach for deftype
:
(defprotocol Edible
(bite-right-side [this])
(bite-left-side [this]))
(deftype WonderlandMushroom []
Edible
(bite-right-side [this]
(str "The bite makes you grow bigger"))
(bite-left-side [this]
(str "The bite makes you grow smaller")))
(deftype RegularMushroom []
Edible
(bite-right-side [this]
(str "The bite tastes bad"))
(bite-left-side [this]
(str "The bite tastes bad too")))
(def alice-mushroom (WonderlandMushroom.))
(def reg-mushroom (RegularMushroom.))
(bite-right-side alice-mushroom)
;; -> "The bite makes you grown bigger"
(bite-right-side reg-mushroom)
;; -> "The bite tastes bad"
the main difference between using protocols with defrecord
and deftype
is how you want your data organized.
if you want structure data, choose defrecord
, otherwise, use deftype
.
Caution
think before you use protocols.
sometimes you could have more simple solution without using protocols.
(def bite-right-side [mushroom]
(if (= (:type mushroom) "wonderland")
"The bite makes you grow bigger"
"This bite tastes bad"))
(bite-right-side {:type "wonderland"})
;; -> "The bite makes you grown bigger"
(bite-right-side {:type "regular"})
;; -> "The bite tastes bad"
Chapter 5. How to Use Clojure Projects and Libraries
to create a new project skeleton:
$ lein new serpent-talk
for namespace and filename, dashes are not being valid in java class names.
so always use understores for directories and filenames, and use dashes for namespaces.
show dependencies as tree:
$ lein deps :tree
run main function of a namespace from the command line using lein run -m
:
$ lein run -m serpent-talk.talk "Hello pigeon"
you can also add this to project.clj
file specifying the main function:
:main serpent-talk.talk
then can run with:
$ lein run "Hello pigeon"
Chapter 6. Communication with core.async
create a new project with core.async
$ lein new async-tea-party
add dependency to project.clj
:
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/core.async "xxx"]]
include core.async
in the namespace (src/async_tea_party/core.clj
file):
(ns async-tea-party.core
(:require [clojure.core.async :as async]))
Basics of core.async Channels
create a channel:
(def tea-channel (async/chan))
there are two main ways that you get things on and off channels: synchronously and asynchronously.
>!!
: a blocking put, puts data on the channel synchronously.<!!
: a blocking take, takes data off the channel synchronously.>!
: an async put, puts data on the channel asynchronously, needs to be used with ago
block.<!
: an async take, takes data off the channel asynchronously, needs to be used with ago
block.
so when you see !!
it means a blocking call.
the tea-channe
created above is an unbuffered channel, the main thread would block until it got taken off. (it will also lock up REPL)
to create a buffered channel:
(def tea-channel (async/chan 10))
now test with the blocking put:
(async/>!! tea-channel :cup-of-tea)
;; -> true
get it off with a blocking take:
(async/<!! tea-channel)
;; -> :cup-of-tea
to close a channel:
(async/close! tea-channel)
this closes the channel to new inputs, however, if there are still values on it, they can be taken off. when the channel is finally empty, it will return a nil
.
(async/>!! tea-channel :cup-of-tea-2)
(async/>!! tea-channel :cup-of-tea-3)
(async/>!! tea-channel :cup-of-tea-4)
(async/close! tea-channel)
;; -> nil
;; new puts will fail
(async/>!! tea-channel :cup-of-tea-5)
;; -> false
;; but existing values can be taken
(async/<!! tea-channel)
;; -> :cup-of-tea-2
(async/<!! tea-channel)
;; -> :cup-of-tea-3
(async/<!! tea-channel)
;; -> :cup-of-tea-4
;; until it's empty
(async/<!! tea-channel)
;; -> nil
also nil
is special, you can not put it on a channel:
(async/>!! tea-channel nil)
;; Exception
so nil
lets us know that the channel is empty.
now try async:
(let [tea-channel (async/chan)]
(async/go (async/>! tea-channel :cup-of-tea-1))
(async/go (println "Thanks for the " (async/<! tea-channel))))
;; Will print to stdout:
;; Thanks for the :cup-of-tea-1