Associative destructuring is similar to sequential destructuring, but applied instead to associative (key-value) structures (including maps, records, vectors, etc). The associative bindings are concerned with concisely extracting values of the map by key.

Let’s first consider an example that extracts values from a map without destructuring:

(def client {:name "Super Co." :location "Philadelphia" :description "The worldwide leader in plastic tableware."}) (let [name (:name client) location (:location client) description (:description client)] (println name location "-" description)) ;= Super Co. Philadelphia - The worldwide leader in plastic tableware.

Note that each line of the let binding is essentially the same - it extracts a value from the map by the name of the key, then binds it to a local with the same name.

Below is a first example of doing the same thing with associative destructuring:

(let [{name :name location :location description :description} client] (println name location "-" description)) ;= Super Co. Philadelphia - The worldwide leader in plastic tableware.

The destructuring form is now a map rather than a vector, and instead of a symbol on the left side of the let, we have a map. The keys of the map are the symbols we want to bind in the let. The values of the destructuring map are the keys we will look up in the associative value. Here they are keywords (the most common case), but they could be any key value - numbers, strings, symbols, etc.

Similar to sequential destructuring, if you try to bind a key that is not present in the map, the binding value will be nil.

(let [{category :category} client] (println category)) ;= nil

Associative destructuring, however, also allows you to supply a default value if the key is not present in the associative value with the :or key.

(let [{category :category, :or {category "Category not found"}} client] (println category)) ;= Category not found

The value for :or is a map where the bound symbol (here category ) is bound to the expression "Category not found" . When category is not found in client , it is instead found in the :or map and bound to that value instead.

In sequential destructuring, you generally bind unneeded values with an _ . Since associative destructuring doesn’t require traversing the entire structure, you can simply omit any keys you don’t plan on using from the destructuring form.

If you need access to the entire map, you can use the :as key to bind the entire incoming value, just as in sequential destructuring.

(let [{name :name :as all} client] (println "The name from" all "is" name)) ;= The name from {:name Super Co., :location Philadelphia, :description The world wide leader in plastic table-ware.} is Super Co.

The :as and :or keywords can be combined in a single destructuring.

(def my-map {:a "A" :b "B" :c 3 :d 4}) (let [{a :a, x :x, :or {x "Not found!"}, :as all} my-map] (println "I got" a "from" all) (println "Where is x?" x)) ;= I got A from {:a "A" :b "B" :c 3 :d 4} ;= Where is x? Not found!

You might have noticed that our original example still contains redundant information (the local binding name and the key name) in the associative destructuring form. The :keys key can be used to further remove the duplication:

(let [{:keys [name location description]} client] (println name location "-" description)) ;= Super Co. Philadelphia - The worldwide leader in plastic tableware.

This example is exactly the same as the prior version - it binds name to (:name client) , location to (:location client) , and description to (:description client) .

The :keys key is for associative values with keyword keys, but there are also :strs and :syms for string and symbol keys respectively. In all of these cases the vector contains symbols which are the local binding names.

(def string-keys {"first-name" "Joe" "last-name" "Smith"}) (let [{:strs [first-name last-name]} string-keys] (println first-name last-name)) ;= Joe Smith (def symbol-keys {'first-name "Jane" 'last-name "Doe"}) (let [{:syms [first-name last-name]} symbol-keys] (println first-name last-name)) ;= Jane Doe

Associative destructuring can be nested and combined with sequential destructuring as needed.

(def multiplayer-game-state {:joe {:class "Ranger" :weapon "Longbow" :score 100} :jane {:class "Knight" :weapon "Greatsword" :score 140} :ryan {:class "Wizard" :weapon "Mystic Staff" :score 150}}) (let [{{:keys [class weapon]} :joe} multiplayer-game-state] (println "Joe is a" class "wielding a" weapon)) ;= Joe is a Ranger wielding a Longbow

Keyword arguments One special case is using associative destructuring for keyword-arg parsing. Consider a function that takes options :debug and :verbose . These could be specified in an options map: (defn configure [val options] (let [{:keys [debug verbose] :or {debug false, verbose false}} options] (println "val =" val " debug =" debug " verbose =" verbose))) (configure 12 {:debug true}) ;;val = 12 debug = true verbose = false However, it would be nicer to type if we could pass those optional arguments as just additional "keyword" arguments like this: (configure 12 :debug true) To support this style of invocation, associative destructuring also works with lists or sequences of key-value pairs for keyword argument parsing. The sequence comes from the rest arg of a variadic function but is destructured not with sequential destructuring, but with associative destructuring (so a sequence destructured as if it were the key-value pairs in a map): (defn configure [val & {:keys [debug verbose] :or {debug false, verbose false}}] (println "val =" val " debug =" debug " verbose =" verbose)) (configure 10) ;;val = 10 debug = false verbose = false (configure 5 :debug true) ;;val = 5 debug = true verbose = false ;; Note that any order is ok for the kwargs (configure 12 :verbose true :debug true) ;;val = 12 debug = true verbose = true The use of keyword arguments has fallen in and out of fashion in the Clojure community over the years. They are now mostly used when presenting interfaces that people are expected to type at the REPL or the outermost layers of an API. In general, inner layers of the code find it easier to pass options as an explicit map.