Clojure

Table of Contents

Elementary

conj

Add an element to this data structure.

do

do is a way of calling multiple functions successively. Here, I'll show you:

(print (let [cnt 1]
  (do
    (println (+ cnt 1))
    (+ cnt 1)
    (println (+ cnt 2))
    (+ cnt 3))))
2
3
4

Notice how the second line within the do does seemingly nothing, but it does indeed print the very last expression (+ cnt 3)! This is because the do evaluates each expression inside and returns the result of the last expression.

The definition of do from the Clojure doc is pretty understandable: Evaluates the expression in order and returns the value of the last. If no expressions are supplied, returns nil.

meta symbol ^

^ is "the meta character". It tells the reader to add the symbol starting with ^ as metadata to the next symbol (provided it is something that implements IMetas).

(def x ^:IamMeta [1 2 3])

x        ;; => [1 2 3]
(meta x) ;; => {:tag :IamMeta}

exclamation mark !

Community Clojure Style Guide recommends that the names of functions/macros that are not safe in STM (Software Transactional Memory) transactions should end with an exclamation mark, !.

Basically, end with ! functions that change state for atoms, metadata, vars, transients, agents and io as well.

Looping

while

loop

Loops in Clojure are wierd, but it's apparently the way it is as to allow for tail-optimization.

If you have a return value, then the way to do it is the following:

(loop [cnt 10]
  (print (str cnt " "))
  (if (zero? cnt)
    cnt
    (recur (dec cnt))))
10 9 8 7 6 5 4 3 2 1 0 

As you can see in the example above, the loop keeps going until we tell it to return some value. recur is the in the else clause of our if-statement and will thus decrement the value of cnt on every iteration until the if-clause is true. Summarized:

  1. Start loop by calling loop on a vector with the initial values (can be multiple initial variables), loop [name1 value1, name2 value2 ... ].
  2. Conditionally call recur for as long as you want to iterate
  3. Return a value to end the loop.

You can also have a loop without returning any value. It's done in the following way:

(loop [cnt 10]
  (when (pos? cnt)
    (print (str cnt " "))
    (recur (dec cnt))))

The difference here is the use of the when macro. In fact, let's expand it!

(println (macroexpand '(when (pos? cnt) (println cnt) (recur (dec cnt)))))
(if (pos? cnt) (do (println cnt) (recur (dec cnt))))

As we can see it actually expands to a if...do.... See section about do for more info on, well, do.

Abstractions and Polymorphism

Multimethods

Multimethods are basically overriding functions, but with one function defined explicitly to figure out which of the methods to use, known as a dispatch function.

(ns were-creatures)

(defmulti full-moon-behavior (fn [were-creature] (:were-type were-creature)))

(defmethod full-moon-behavior :wolf
  [were-creature]
  (str (:name were-creature) " will howl and murder"))

(defmethod full-moon-behavior :simmons
  [were-creature]
  (str (:name were-creature) " will encourage people and sweat to the oldies"))

(println (full-moon-behavior {:were-type :wolf
                              :name "Rachel from next door"}))
(println (full-moon-behavior {:were-type :simmons
                     :name "Andy the baker"}))
Rachel from next door will howl and murder
Andy the baker will encourage people and sweat to the oldies

You can also specify a default method using the dispatch value :default.

(using '(were-creatures))

(defmethod full-moon-behavior :default
  [were-creature]
  (str (:name were-creature) " will stay up all night fantasy footballing"))

(println (full-moon-behavior {:were-type :office-worker
                     :name "Jimmy from sales"}))
Jimmy from sales will stay up all night fantasy footballing

Clojure also allows for dispatching on multiple arguments, ordering the methods by preferral, dispatching based on type, and hierarchical dispatching.

NOTE: Only one of the multimethods can have rest (i.e. &args) arguments.

Hierarchical dispatching

The hierarchy system supports derivation relationships between names (either symbols or keywords), and relationships between classes and names.

derive function creates these relationships, and isa? (as in "is a?") function tests for their existence.

Note the :: reader syntax. ::keyword resolve a namespace.

derive is the fundamental relatsionship-maker

(derive ::rect ::shape)
(derive ::square ::rect)

parent / ancestors / descendants and isa? let you query the hierarchy

(parents ::rect)
; => #{:user/shape}

(ancestors ::square)
; => #{:user/rect :user/shape}

(descendants ::shape)
; => #{:user/rect :user/square}

(isa? ::square ::shape)
; => true

Protocols

Protocols are optimized for dispatch methods according to type, but it can also be done using multimethods. One limitation though, is that a protocol cannot have method signatures with rest arguments.

I personally think protocol looks very much like an interface, from other languages.

Cool thing about a protocol is that it allows you to extend any type, both in Java and in Clojure!

(ns data-psychology)

(defprotocol Psychodynamics
  "Plumb the inner depths of your data types"
  (thoughts [x] "The data type's innermost thoughts")
  (feelings-about [x] [x y] "Feelings about self or other"))

(extend-type java.lang.String
  Psychodynamics
  (thoughts [x] (str x " thinks, 'Truly, the character defines the data type'"))
  (feelings-about
    ([x] (str x " is a longing for a simpler way of life"))
    ([x y] (str x " is envious of " y "'s simpler way of life"))))

(println (thoughts "blorb"))
(println (feelings-about "schmorb"))
(println (feelings-about "schmorb" 2))
blorb thinks, 'Truly, the character defines the data type'
schmorb is a longing for a simpler way of life
schmorb is envious of 2's simpler way of life

As you can see, the defprotocol takes the following:

  • Name of the protocol
  • Optional docstring
  • Method signatures have the name of the method, arguments and a optional docstring

You can also do extend-protocol instead of extend-type, like this:

(using '(data-psychology))

(extend-protocol Psychodynamics
  java.lang.String
  ...

  java.lang.Object
  ... )

NOTE: If you want a "default" implementation for all classes, you simply implement the protocol for java.lang.Object, since everything in Java, and thus Clojure, inherits from this.

Datatypes

The datatype features - deftype, defrecord and reify, provide the mechanism for defining implementations of abstractions, and in the case of reify, instances of those implementations.

The abstractions themselves are defined either by protocols or interfaces.

A datatype provides a host type, (named in the case of deftype and defrecord, anonymous in the case of reify), with some structure (explicit fields in the case of deftype and defrecord, implicit closure in the case of reify), and optional in-type implementations of abstraction methods.

Records

A record is a custom map-like data type. You associate keys with values, can look up values the same way as you do with a map, and they're immutable like a map.

The difference is that you can specify fields for a record, and you can extend a record to implement a protocol.

(ns were-records)

(defrecord WereWolf [name title])

;; Different ways to instatiate a record
(WereWolf. "David" "London Tourist")
(->WereWolf "Jacob" "Lead Shirt Discarder")
(map->WereWolf {:name "Lucian" :title "CEO of Melodrama"})

And accessing properties on a record is done as usual

(use '(were-records))

(def jacob (->WereWolf "Jacob" "Lead Shirt Discarder"))

(println (.name jacob))
(println (:name jacob))
(println (get jacob :name))
Jacob
Jacob
Jacob

Any function you can use on a map can also be used on a record.

(use '(were-records))

(assoc jacob :title "Lead Third Wheel")
nil#were_records.WereWolf{:name "Jacob", :title "Lead Third Wheel"}

And for extending a protocol using a record, you can do it the usual way, as seen above, or you can do it on the definition of the record.

(defprotocol WereCreature
  (full-moon-behavior [x]))

(defrecord WereWolf [name title]
  WereCreature
  (full-moon-behavior [x]
    (str name " will howl and murder")))

(println (full-moon-behavior (map->WereWolf {:name "Lucian" :title "CEO of Melodrama"})))
Lucian will howl and murder

Final note: accessing a record is actually more performant that accessing a map. Wierd, huh?

Reify

reify defines both an anonymou type and creates an instance of that type. The use case is where you need a one-off implementation of one or more protocols or interfaces and would like to take advantage of local context.

Concurrency and Mutability

Atoms

Agents

Refvars

Leiningen