Clojure: Binding Forms, Destructuring

By Xah Lee. Date: . Last updated: .

Clojure “binding form” is the syntax for local variable assignment.

Clojure “binding form” lets you assign multiple variables from a list/map structure. This is sometimes called destructuring binding.

Here's a example. Suppose you have a function

(defn norm
  "given [x, y], return sqrt[x^2+y^2]"
  [coordinate]
  (let [x (nth coordinate 0),
        y (nth coordinate 1)]
    (Math/sqrt (+ (Math/pow x 2) (Math/pow y 2)) )))

(norm [3 4])                            ; 5.0

Here's the same function using convenient binding form. (no need to manually extract the elements “nth”)

(defn norm2
  "given [x, y], return sqrt[x^2+y^2]"
  [coordinate]
  (let [[x y]
        coordinate]
    (Math/sqrt (+ (Math/pow x 2) (Math/pow y 2)))))

Here's the same function using even a more convenient binding form, in function parameter directly:

(defn norm3
  "given [x, y], return sqrt[x^2+y^2]"
  [[x y]]
  (Math/sqrt (+ (Math/pow x 2) (Math/pow y 2)) ))

Binding forms are used in:

Note: when using “let”, you have pairs of variable and value. When in function's parameter binding, you don't need the value part, because that will come from function call. (this is why, in our example above, the norm3 version doesn't mention “coordinate” at all.)

Clojure binding form has complex syntax variation and is very powerful. The following are details.

Binding Sequence Types

Here's a destructure binding on vector data type:

(let [[a b] [3 4]] ; a gets 3, b gets 4
  [a b]            ; [3 4]
  )

Binding Nested Structure

the binding form can be nested.

(let [
      [[a b] [c d]] ; ← variables structure, matches
      [[1 2] [3 4]] ; ← values structure
      ]
  [a b c d] ;  [1 2 3 4]
  )

Binding Rest Args

use & x in the variable list to get the rest of values into x, as a list data type.

(let [[a b c & d] 
      [1 2 3 4 5 6 7]]
  [a b c d] ; [1 2 3 (4 5 6 7)]
  )

Bind Entire Input

sometimes you also want a variable that's the entire input.

On the variable side, you can add … :as x so that the value of x is the entire right hand side.

(let [[a b :as x] 
      [1 2 3 4]]
  [a b x] ; [1 2 [1 2 3 4]]
  )
(let [[[a b] [c d] :as x]
      [[1 2] [3 4]]]
  [a b c d x] ;  [1 2 3 4 [[1 2] [3 4]]]
  )

Binding String, List

Destructure bind also works on string, list, sequence, array. The right hand side can be anything that supports nth function. clojure.core/nth

(let [[a b] "xyz123"]
  [a b] ; [\x \y]
)
;; note: \x is Clojure syntax for character datatype
(let [[a b & c :as d] "xyz123"]
  [a b c d] ; [\x \y (\z \1 \2 \3) "xyz123"]
)

here's a example of destructure binding with list data type.

(let [[a b & c] '(1 2 3 4 5 6)]
  [a b c] ; [1 2 (3 4 5 6)]
  )

Map Binding Form

Sometimes the input is a map (list of key/val pairs). You want to extract some of the keys in the map to assign to variables.

Clojure binding form let you conveniently extract map for variable assignment.

;; a map
(def mm {:a 5 :d 8 :c 6})

;; set x to the value of key :d
(let [{x :d} mm]
  x ; 8
  )

if the key doesn't exist, you get nil.

(let [{h :b} {:a 5 :c 6}]
  h ; nil
  )

you can supply default values by :or {var1 key1, var2 key2, …} if a key doesn't exist.

;; a map
(def mm {:a 5 :c 6})

(let [{a :a, b :b, c :c, :or {a 2 b 3}} mm] ; default a=2, b=3
  [a b c] ; [5 3 6]
  )

You can also use :as x to assign the entire map to a extra variable.

(let [{x :c, y :a, :as m}  {:a 5 :c 6 :b 4}]
  [x y m] ; [6 5 {:c 6, :b 4, :a 5}]
  )

the :as and :or can be used together.

(let [{a :a, b :b, c :c, :as m :or {a 2 b 3}}  {:a 5 :c 6}]
  [a b c m] ; [5 3 6 {:c 6, :a 5}]
  )

Extract Sequence Input by Index

the map binding form {…} also works with sequence types. The keys are 0, 1, 2, 3….

(def xx ["a" "b" "c"])

(let [{x 1} xx]
  x ; "b"
  )

Shortcut Syntax for Destructure Binding with Map

often, you want the variables to have the same name as the keys in map. There's a shortcut syntax.

binding with :keys

this binding form

[{x :x, y :y} {…}]

can be written as:

[{:keys [x y]} {…}]

example

(def mm {:t 3, :b 5, :a 4})

(let [
      {a :a, b :b}
      mm
      ]
  [a b] ; [4 5]
  )

;; is equivalent to

(let [
      {:keys [a b]}
      mm
      ]
  [a b] ; [4 5]
  )

Binding Map of Symbol Keys

Sometimes, your map is key'd by lisp symbol, for example: {'a 3, 'b 4}. You can use :syms instead of :key

(def mm {'t 3, 'b 5, 'a 4})

(let [
      {a 'a, b 'b}
      mm
      ]
  [a b] ; [4 5]
  )

;; is equivalent to

(let [
      {:syms [a b]}
      mm
      ]
  [a b] ; [4 5]
  )

Binding Map of String Keys

Sometimes, your map is key'd by string, for example: {"a" 3, "b" 4}. You can use :strs instead of :key

(def mm {"t" 3, "b" 5, "a" 4})

(let [
      {a "a", b "b"}
      mm
      ]
  [a b] ; [4 5]
  )

;; is equivalent to

(let [
      {:strs [a b]}
      mm
      ]
  [a b] ; [4 5]
  )

Reference

http://clojure.org/reference/special_forms#binding-forms