In Clojure, you can access map entries in a number of useful ways. First, a map
can be called as a function with a key as the only argument. It will return the
value associated with the key, or nil
, if there are none:
(def person {:name "Christian"})
(person :name) (person :age)
Usually, maps use keywords for keys. Keywords can also be used as functions,
taking a map as their only argument:
(def person {:name "Christian"})
(:name person) (:age person)
This may be subjective, but to me this reads better. However, the real reason
why this is cool is because of this:
(nil :key)
(:key nil)
nil
In other words, using keywords to access map items produces nil
if your map is
actually nil
. This turns out to be very practical.
Here's another wicked cool thing about keywords-as-functions:
(def people [{:name "John Doe"}
{:name "Jane Doe"}])
(map :name people)
["John Doe" "Jane Doe"]
Whenever you have nested maps (or vectors in maps in vectors for that matter),
Clojure offers multiple functions to make life easier. The first is
get-in
:
(def person {:name "Christian"
:address {:country "Norway" :city "Oslo"}})
(get-in person [:name])
"Christian"
(get-in person [:address :country])
"Norway"
(get-in person [:address :street])
nil
(get-in nil [:address :street])
nil
get-in
also sorta works when maps and vectors mix:
(def people [{:name "Christian" :hobbies [{:name "Beer"}
{:name "Food"}
{:name "Music"}]}])
(get-in people [0 :hobbies 1 :name])
Beware though! Using get-in
on a lazy sequence will not work as expected.
There are functions available to help you "set" nested map entries as well.
assoc-in
is used to put a value into the map/vector structure
somewhere, while update-in
can be used to set a value based on the
old one (e.g. you provide a function that receives the old value as its
argument, and may return the "new" value):
(assoc-in person [:address :street] "Street-o-rama")
{:name "Christian",
:address {:country "Norway", :city "Oslo", :street "Street-o-rama"}}
(update-in person [:name] (fn [name] (.toUpperCase name)))
{:name "CHRISTIAN", :address {:country "Norway", :city "Oslo"}}
The anonymous function literal is
a dispatch macro. Basically, it is a very
compact way of creating functions, perfect for those cases where you need a
quick one-off function. It is perfect for use with the aforementioned
update-in
.
(update-in person [:name] (fn [name] (.toUpperCase name)))
{:name "CHRISTIAN", :address {:country "Norway", :city "Oslo"}}
(update-in person [:name] #(.toUpperCase %))
{:name "CHRISTIAN", :address {:country "Norway", :city "Oslo"}}
By using the anonymous function literal, we avoid one nested form. If the
function only receives one argument, you can refer to it as %
. If the function
should take more arguments, refer to them with %1
, %2
etc. %
is the same
as %1
, but I prefer not to mix (e.g. %
and %2
in the same form looks
inconsistent and unappealing to me).
The anonymous function literal is also a good match for map. Let's say you are
looking for the number of leading spaces on each line in a block of text. You
might write it like so:
(map (fn [line] (count (re-find #"^ +" line))) lines)
In short concise code snippets like this, the function contributes unnecessary
amounts of noise. The anonymous function literal can clean it up:
(map #(count (re-find #"^ +" %)) lines)
This has less noise, and communicates the mapping much clearer.
The thread-first macro is a feature that is designed to help you untangle stuff
like this:
(prepare-pages
(pages/get-pages
(content/cultivate-content
(validate-raw-content
(content/load-content)))))
Using the thread-first macro, we can break up this deeply nested structure and
represent it as a pipeline instead:
(-> (content/load-content)
validate-raw-content
content/cultivate-content
pages/get-pages
prepare-pages)
This is easier to read because it is sequential instead of nested. It is also
easier to manipulate.
The thread-first macro gets its name from the fact that it threads the previous
value in as the first argument to the next form. This becomes clear if any of
the forms actually take arguments:
(-> person
:address
(assoc :street "Street-o-rama")
(assoc :city "Gotham"))
Here we're also using keywords-as functions. This form means the same as this:
(assoc (assoc (:address person) :street "Street-o-rama") :city "Gotham")
I think it is fairly uncontroversial to say that the thread-first form is way
easier to understand.
Remember the not-so-optimal example of using get-in
on a potentially lazy
sequence? We can do the same thing using the threading macro for an elegant
solution that is also performant:
(get-in people [0 :hobbies 1 :name])
(-> people first :hobbies second :name)
This will still silently return nil
if for instance this person has no
hobbies.
The thread-last macro is just like the thread-first, except it threads values as
the last argument to next form:
(->> lines
(remove empty?)
(map #(count (re-find #"^ +" %)))
(min*))
(min* (map #(count (re-find #"^ +" %)) (remove empty? lines)))
If you like these, you'll be pleased to know
that Clojure 1.5
shipped with even more threading macros, such as as->
, which allows you to
choose and position the placeholder yourself.
These are just some of the details I love about Clojure, but I use them a lot,
and they are for me a big part of what makes Clojure so effortless to work with.