Beautiful Clojure – Destructuring data

What is destructuring?

Destructuring is one of the beautiful features which impressed me, when learning Clojure. It helps you to assign individual elements of a vector/list or map to variables. You can destructure function parameters as well as return values.

A first example

Imagine a function which returns the age of the youngest and the oldest person in a sport club.

(defn find-min-max-age [club]
  (let [min-age (calculate-min-age club)
        max-age (calculate-max-age club)]
    [min-age max-age]))

The vector which is returned can be assigned directly to two variables: min and max.

(let [[min max] (find-min-max-age "Swim Club")]
  (println "Youngest member is " min)
  (println "Oldest member is " max))

I like this elegant approach a lot. It is much more compact as a comparable Java version, which requires additional code to deal with null values.

    Integer[] minMax = findMinMax("Swim Club");
    Integer min = minMax.length > 0 ? minMax[0] : null;
    Integer max = minMax.length > 1 ? minMax[1] : null;
    System.out.println("Youngest member is " + min);
    System.out.println("Oldest member is " + max);

More vector examples

You can destructure function parameters as well. It just works every where.

(defn find-team-member [[min max]] 
	(println min max))
	
; Calling the function
(find-team-member [5 20])

As well, you can ignore values. To ignore values at the end of the vector, just leave off the variables.

(let [[min] (find-min-max-age "Rugby Club")]
  (println "Youngest member is" min))

To ignore values at a specific place, just use _ as the variable name. It is a common convention.

(let [[_ max] (find-min-max-age "Kite Surfing Club")]
  (println "Oldest member is" max))

Destructuring maps

Clojure separates functions and data. Maps are frequently used to store data and they can be destructured as well.

The following function expects a map as parameter. It extracts the values for the keys min and max and assigns them to variable named min, max respectively.

(defn find-team-member[ {:keys [min max]} ] 
	(println min max))

; Let us use our function
(find-team-member {:min 5 :max 10})

The destructuring of a map is expressed as a map. What could be more concise?

If you need to get hold of the whole map, you can add a special key :as to the map.

(defn find-team-member[ {:keys [min max] :as all-data} ] 
	(println min max all-data))

; Let us use our function
(find-team-member {:min 5 :max 10 :foo "some value"})

Default values for maps

To specify default values for maps, just add a key :or and a map describing those values.

(defn find-team-member[ {:keys [min max] :or {min 3 max 20}}] 
	(println min max))

I hope you enjoyed the article.