Jim Cheung

vectors, lists, maps and sets

Vectors and maps have a lot in common. They both associate keys with values, the difference being that with vectors the keys are limited to integers while in maps the keys can be more or less anything. That is, in fact, how Clojure looks at vectors—which means that many of the functions that work with maps will also work with vectors. For example, assoc and dissoc work fine on vectors. Thus (assoc [:title :by :published] 1 :author) will give you [:title :author :published].

the maps that you create with the literal {} or the hash-map function make no promises about the order of their keys. There is a second flavor of map that keeps its keys sorted. You can make one of these sorted maps with the aptly named function sorted-map.

the order of values returned from vals is arbitrary, but it’s guaranteed to match the order of the keys returned by the keys function.

Like maps, sets have their own ideas about the order of their elements. The set that you wrote as #{:sci-fi :romance :mystery} is liable to come back to you as #{:sci-fi :mystery :romance}.

You can create a larger set from an existing set with conj; remove elements with disj

(#{"derby" "h2" "hsqldb" "sqlite"} subprotocol)

this is a test to see if the value bound to subprotocol is the name of a database that we recognize

If you’re worried that you may have a set with nil as a member, use contains? to check for membership

logic

Under the hood, = is identical to the Java equals method.

The rule is simple: in an if statement and any other Boolean context, only false and nil get treated as false. Everything else is treated as true.

cond takes pairs of expressions, each pair made up of a predicate expression and a value expression

(defn shipping-charge [preferred-customer order-amount]
 	  (cond
 	    preferred-customer 0.0
 	    (< order-amount 50.0) 5.0
 	    (< order-amount 100.0) 10.0
 	    :else (* 0.1 order-amount)))

case, which based on a single value.

(defn customer-greeting [status]
 	  (case status
 	    :gold       "Welcome, welcome, welcome back!!!"
 	    :preferred  "Welcome back!"
 	                "Welcome to Blotts Books"))

If nothing matches, then the expression evaluates to the last, unpaired expression—in this case "Welcome to Blotts Books".

And the easiest way to get hold of an exception value is to use the built-in ex-info function:

(defn publish-book [book]
 	  (when (not (:title book))
 	    (throw
 	      (ex-info "A book needs a title!" {:book book})))
 	
 	  ;; Lots of publishing stuff...
 	  )

The ex-info function takes a string describing the problem and a (possibly empty) map containing any other pertinent information.

if you want to catch an exception generated by ex-info you will need to look for exceptions of type clojure.lang.ExceptionInfo.

multimethod

(defn dispatch-book-format [book]
 (cond
  (vector? book) :vector-book
  (contains? book :title) :standard-map
  (contains? book :book) :alternative-map))

(defmulti normalize-book dispatch-book-format)

(defmethod normalize-book :vector-book [book]
 {:title (first book) :author (second book)})
	
(defmethod normalize-book :standard-map [book]
 book)
	
(defmethod normalize-book :alternative-map [book]
 {:title (:book book) :author (:by book)})

if the dispatch function produces a value for which there is no corresponding defmethod, Clojure will generate an exception. Alternatively, you can supply a method for the value :default that will cover the everything else case.

multimethod addition does not have to appear in the same file

pre and post conditions

To set up a :pre condition just add a map after the arguments—a map with a :pre key. The value should be a vector of expressions.

(defn publish-book [book]
 	  {:pre [(:title book) (:author book)]}
 	  (print-book book)
 	  (ship-book book))

You can even take the checking one step further by specifying a :post condition, which lets you check on the value returned from the function.

(defn publish-book [book]
 	  {:pre  [(:title book) (:author book)]
 	   :post [(boolean? %)]}
 	  (print-book book)
 	  (ship-book book))

functions

One more example of a function-generating function is every-pred. It combines predicate functions into a single function that ands them all together.

(def cheap-horror-possession?
 	  (every-pred
 	    cheap?
 	    horror?
 	    (fn [book] (= (:title book) "Possession"))))

def

you can get at both the symbol and the value buried inside of the var:

(def author "Austen")  ; Make a var.

#'author               ; Get at the var for author -> "Austen".

(def the-var #'author) ; Grab the var.

(.get the-var)         ; Get the value of the var: "Austen"
(.-sym the-var)        ; Get the symbol of the var: author

dynamic vars

there is a Clojure convention for naming dynamic vars. The convention is that dynamic vars should begin and end with * (earmuffs)

(def ^:dynamic *debug-enabled* false)
 	
 	(defn debug [msg]
 	  (if *debug-enabled*
 	    (println msg)))
 	
 	(binding [*debug-enabled* true]
 	  (debug "Calling that darned function")
 	  (some-troublesome-function-that-needs-logging)
 	  (debug "Back from that darned function"))

set!, which changes the value of a dynamic var from inside the binding.

(set! *print-length* 2)