Clojure Idioms, Patterns, and Style
Checking For An Empty List
Don’t use (not (empty? coll))
. Instead prefer (seq coll)
instead.
Seq returns nil if the collection is empty. If the seq isn’t empty then it returns a seq on that collection.
Flattening The Result Of A Map By One Level
If the result of a map
function ever returns a list of lists, then
use mapcat
over (apply (concat (map...)))
:
(map list (range 10))
;; => ((0) (1) (2) (3) (4) (5) (6) (7) (8) (9))
(mapcat list (range 10))
;; => (0 1 2 3 4 5 6 7 8 9)
Maps Are Functions
Maps, like keywords, are functions.
You can access a map using a keyword as the function, the map as the
function, or get
as the function:
(def m {:a 1 :b 2})
(m :a) ;; => 1
(:a m) ;; => 1
(get m :a) ;; => 1
If you don’t need to return a default value, in which you would use
the get
function, it’s preferable to use a keyword as the function
instead of the map (the middle version).
This protects you from possible null pointer exceptions. Keywords can never be nil, while you can have a nil bound to what your map is.
Using a keyword to access nil
returns nil. However, using nil
to
access a keyword is a null pointer exception.
(nil :a) ;; CompilerException java.lang.IllegalArgumentException: Can't call nil, form: (nil :a)
(:a nil) ;; => nil
Sets Are Also Functions
Similarly to maps, sets, while not used nearly as often as maps, are also functions.
You can define sets using the set
function or #{}
syntax.
(def key-set #{:a :b})
(key-set :a) ;; => :a
(key-set :b) ;; => :b
(key-set :c) ;; => nil
(:a key-set) ;; => :a
(:b key-set) ;; => :b
(:c key-set) ;; => nil
It returns the value if is present within the set and nil if it isn’t.
This property allows us do a few interesting things.
Don’t use contains?
on a set
If you have a set, you don’t need to use the contains?
function to
see if a key is present. Just use the set as the function.
(def key-set #{:a :b})
;; Don't do this
(contains? key-set :a) ;; => true
;; Prefer this
(key-set :a) ;; => :a
The (every? key-set (keys my-map))
Idiom
(def key-set #{:a :b})
(every? key-set [:a :b])
This is useful in testing JSON responses for example. You can assert that the keys of the response map returned is within the set of keys you expected to return.
(def endpoint-response {:name "Fat Tony"
:address-line-1 "123 Fake St."
:zip "60606"})
(def expected-keys #{:name :address-line-1 :zip})
(every? expected-keys (keys endpoint-response)) ;; => true
Conditionally Assoc’ing Onto A Map
If you find yourself using threading macros like ->
or ->>
on a
map and need to conditionally assoc/dissoc, use cond->
(def m {:a 1})
(cond-> m
:a (assoc :b 2)) ;; => {:a 1, :b 2}
Master GitHub Actions with a Senior Infrastructure Engineer
As a senior staff infrastructure engineer, I share exclusive, behind-the-scenes insights that you won't find anywhere else. Get the strategies and techniques I've used to save companies $500k in CI costs and transform teams with GitOps best practices—delivered straight to your inbox.
Not sure yet? Check out the archive.
Unsubscribe at any time.