💾 Archived View for rwv.io › articles › clojure-destructuring.gmi captured on 2022-04-29 at 11:22:07. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
These are my notes for a talk I prepared for a clojure user group meeting.
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
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.
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
Instead of the unspliced:
> ((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}
> (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} :added "1.0"} ([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; }
https://github.com/clojure/clojure/blob/clojure-1.7.0/src/jvm/clojure/lang/RT.java#L719
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"
Use your own types!
> (deftype Stuff [n] clojure.lang.ILookup (valAt [this k] ({:wings n :coolness (* n 3.14) :speed (/ n 1.33)} k))) > (let [{speed :speed} (Stuff. 5)] speed) 3.7593984962406015
Pretty cool, right? Thanks for listening.
--
📅 2014-04-15
📧 hello@rwv.io
🅭 BY-NC-SA 4.0