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}

Join the 80/20 DevOps Newsletter

If you're an engineering leader or developer, you should subscribe to my 80/20 DevOps Newsletter. Give me 1 minute of your day, and I'll teach you essential DevOps skills. I cover topics like Kubernetes, AWS, Infrastructure as Code, and more.

Not sure yet? Check out the archive.

Unsubscribe at any time.