Make the Clojure destructuring guide into code.
(let->destructured-let code-string)
Takes a string of code containing only a let statement.
Produces a result that comprises data attached to the following keys:
:inputs - the input data and spec
:parse - the fully parsed form
:analysis - details of the bindings
:transform - perform the destructuring logic by applying the analysis data to the parsed data
:unform - the unform data and the unformed form
The unformed form can be evaluated
You can see expected usage patterns in the tests.
(let [in-bindings '(let [m {:a/k1 1 :b/k2 2 :c/k3 3}
k1 (get m :a/k1)
k2 (:b/k2 m)]
(+ k1 k2))]
(->> (pr-str in-bindings)
let->destructured-let))
=>
{:inputs {:string-form "(let [m {:a/k1 1, :b/k2 2, :c/k3 3} k1 (get m :a/k1) k2 (:b/k2 m)] (+ k1 k2))",
:edn-form (let [m {:a/k1 1, :b/k2 2, :c/k3 3} k1 (get m :a/k1) k2 (:b/k2 m)] (+ k1 k2)),
:spec :destructive.destructure/form},
:parse {:form-name :let,
:parsed-form {:name let,
:bindings [{:form [:local-symbol m], :init-expr {:a/k1 1, :b/k2 2, :c/k3 3}}
{:form [:local-symbol k1], :init-expr (get m :a/k1)}
{:form [:local-symbol k2], :init-expr (:b/k2 m)}],
:exprs (+ k1 k2)},
:bindings-symbols {m {:literal {:a/k1 1, :b/k2 2, :c/k3 3}},
k1 {:form-name :get,
:parsed-form {:name get, :map [:symbol m], :key :a/k1},
:key :a/k1,
:map {:ref m}},
k2 {:form-name :lookup,
:parsed-form {:key :b/k2, :map [:symbol m]},
:key :b/k2,
:map {:ref m}}}},
:analysis {:bindings {:map-accessors {m [{:symbol m,
:accessor k1,
:key {:keyword :a/k1, :name "k1", :namespace "a"}}
{:symbol m,
:accessor k2,
:key {:keyword :b/k2, :name "k2", :namespace "b"}}]}}},
:transform {:bindings [{:form [:local-symbol m], :init-expr {:a/k1 1, :b/k2 2, :c/k3 3}}
{:form [:map-destructure {:a/keys [k1], :b/keys [k2]}], :init-expr m}]},
:unform {:unform-form {:name let,
:bindings [{:form [:local-symbol m], :init-expr {:a/k1 1, :b/k2 2, :c/k3 3}}
{:form [:map-destructure {:a/keys [k1], :b/keys [k2]}],
:init-expr m}],
:exprs (+ k1 k2)},
:unformed (let [m {:a/k1 1, :b/k2 2, :c/k3 3}
{:a/keys [k1], :b/keys [k2]} m] (+ k1 k2))}}To see how far spec will take us in parsing and analysing Clojure code.
Although it's incomplete and buggy, we have it shipped with Clojure.
Yeah, when it's "done" we (or anyone else) can do this again with spec2.
Editors operate on strings and this tool should be usable from an editor.
- Parse let and get
- Bindings, top level keys
- key access in bindings
- multiple unqualified key access
- namespaced key access
- multiple namespaced key access
- renamed keys
- namespaced keys with binding X are renamed
- binding to missing key produces nil
- default binding results (
:or)
- :strs and :syms for string and symbol keys respectively & tests
- namespaced :syms
- Bindings, nested keys
- unqualified key access in bindings
- merge multiple unqualified key access
- namespaced key access
- merge multiple namespaced key access
- Become motivated to do this
I found this detailed post from Yehonathan Sharvit.
It was especially useful in understanding how to manipulate the data that is produced by conform and ensure that it stays well-formed for the unform function.
I was stuck with how qualified keys are emitted.
Thanks to Lasse Määttä who suggested using print-namespace-maps via #clojure-spec on the Clojurian Slack
I was pulling my hair our cos I thought this would be simple but I was stuck.
Thanks to Thomas Moerman who is a hella smart and banged out the inverted-merge function and pasted it into the Clojurian Slack
Thanks to Alex Miller from the core team for suggesting that spec2 is dormant not dead.
Kinda reminded me of the Monty Python Norwegian parrot sketch, and hopefully we will get a better result.
In the meantime, we'll make the most of what we have, and won't be pining for the fjords.