# Destructuring aka "abstract structural binding"

These are my notes for a talk I prepared for a clojure user group meeting.

## What is it?

Sometimes said to be clojures way to do named parameters but it's much more than that. It is a way to take apart a structure into multiple substructures typically when assigning variables.

Let's dive in with examples! It works on lists:

```> (let [[x y z] [1 2 3]])
y)
2
> (let [[x y & z] [1 2 3 4]]
z)
(3 4)
```

and on maps:

```> (let [{z :z} {:x 1 :y 2 :z 3}]
z)
3
```

It goes deeper into the structure:

```> (let [{{x :x} :y} {:y {:x 1}}] ; symmetry!
x)
1
> (let [[_ _ [_ _ [x]]] [1 [2] [3 4 [5]]]]
x)
5
> (let [[_ _ [_ _ [{z :z}]]] [1 [2] [3 4 [{:x 1 :y 2 :z 3}]]]]
z)
3
```

and there's the fancy stuff like `keys`, `strs` and `syms`:

```> (let [{:keys [x y]} {:x 1 :y 2}]
y)
2
> (let [{:strs [x y]} {"x" 1 "y" 2}]
y)
2
> (let [{:syms [x y]} {'x 1 'y 2}]
y)
2
```

Also `as` and `or`:

```> (let [[x y :as all] [1 2]]
[x all])
[1 [1 2]]
> (let [{:keys [x y z] :as all} {:x 1 :y 2}]
[x all])
[1 {:x 1, :y 2}]
> (let [{:keys [x y z] :or {z 3}} {:x 1 :y 2}]
z)
3
```

## Can it be used outside of `let`?

Yes, many expressions support it, like `defn` or rather `fn`:

```> ((fn [{x :x}] x) {:x 5})
5
```

And many more, like `loop`, `doseq`:

```> (loop [[val & coll] [1 2 3]]
(if (even? val)
val
(if (seq coll)
(recur coll))))
2
> (doseq [{x :x} [{:a 1} {:x 2}]]
(prn x))
nil
2
```

Anything in core having some kind of binding, params, seq-exprs supports this.

## How does it work?

Let's investigate! What is let?

```> (source let)
(defmacro let
"binding => binding-form init-expr

Evaluates the exprs in a lexical context in which the symbols in
the binding-forms are bound to their respective init-exprs or parts
therein."
{:added "1.0", :special-form true, :forms '[(let [bindings*] exprs*)]}
[bindings & body]
(assert-args let
(vector? bindings) "a vector for its binding"
(even? (count bindings)) "an even number of forms in binding vector")
`(let* ~(destructure bindings) ~@body))
```

So what happens when we use it?

```> (macroexpand '(let [{x :x y :y} val]))
(let* [map__2604 val
map__2604 (if (clojure.core/seq? map__2604)
(clojure.core/apply clojure.core/hash-map map__2604)
map__2604)
x (clojure.core/get map__2604 :x)
y (clojure.core/get map__2604 :y)])
```

The destructure function?

```> (destructure [{'x :x 'y :y} 'val])
[map__2173 val
map__2173 (if (clojure.core/seq? map__2173)
(clojure.core/apply clojure.core/hash-map map__2173)
map__2173)
x (clojure.core/get map__2173 :x)
y (clojure.core/get map__2173 :y)]
> (destructure [['a 'b '& 'c] [1 2 3 4]])
[vec__2020 [1 2 3 4]
a (clojure.core/nth vec__2020 0 nil)
b (clojure.core/nth vec__2020 1 nil)
c (clojure.core/nthnext vec__2020 2)]
```

Any seq will do?

```> (let [{x :x} '(:x 1 :y 2)] x)
1
```

Really seq?

```> ((fn [& x] x) 1)
(1)
> (seq? ((fn [& x] x) 1))
true
```

Aha! Finally! Named parameters!

```> ((fn [& {x :x}] x) :x 1 :y 2)
1
```

```> ((fn [{x :x}] x) {:x 1 :y 2})
1
```

What about the doc string of my new function?

```> (defn ^{:doc "bla bla bla" :arglist ["(:x SOME-X)? (:y SOME-Y)?"]}..
```

What about all the named parameters?

```> ((fn [& {:as options}] options) :x 1 :y 2)
{:x 1 :y 2}
```

## Only lists and maps?

```> (source get)
(defn get
"Returns the value mapped to key, not-found or nil if key not present."
{:inline (fn  [m k & nf] `(. clojure.lang.RT (get ~m ~k ~@nf)))
:inline-arities #{2 3}
([map key]
(. clojure.lang.RT (get map key)))
([map key not-found]
(. clojure.lang.RT (get map key not-found))))
```

Hmm, let's look at RT.java:

```static public Object get(Object coll, Object key){
if(coll instanceof ILookup)
return ((ILookup) coll).valAt(key);
return getFrom(coll, key);
}

static Object getFrom(Object coll, Object key){
if(coll == null)
return null;
else if(coll instanceof Map) {
Map m = (Map) coll;
return m.get(key);
}
else if(coll instanceof IPersistentSet) {
IPersistentSet set = (IPersistentSet) coll;
return set.get(key);
}
else if(key instanceof Number && (coll instanceof String || coll.getClass().isArray())) {
int n = ((Number) key).intValue();
if(n >= 0 && n < count(coll))
return nth(coll, n);
return null;
}

return null;
}
```

Anything we can "get" or "nth"; strings!

```> (let [[_ x] "yelp"]
x)
\e
>  (let [{x 2} "yelp"]
x)
\l
> (let [[_ & x] "yelp"]
(apply str x))
"elp"
> (let [[c & r] "yelp"]
(apply str (Character/toUpperCase c) r))
"Yelp"
```

```> (deftype Stuff [n] clojure.lang.ILookup