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)