Clojure: destructure and rename with {:keys [...]} - clojure

Is it possible to both destructure and rename keys in one go?
Consider this:
(let [{:keys [response]} {:response 1}]
(println response))
However, if I want to instead refer to 1 as my-response, I have to do something like:
(let [{:keys [my-response]} (clojure.set/rename-keys {:response 1} {:response :my-response})]
(println my-response))
Obviously this does not work with defn destructuring.
Is there any way in Clojure to both destructure and rename keys?

Use destructuring without :keys:
(let [{my-response :response} {:response 1}]
(println my-response))
{:keys [response]} is syntactic sugar for {response :response}.

Here you go:
(let [{:keys [response]} {:response 1}
my-response response]
(println my-response))
For a better answer refer to https://stackoverflow.com/a/57592661/2757027.
This answer is much closer to the question, but technically not single step. but this doesn't involve any complicated de-structuring.

If you don't mind using a library, a more powerful destructuring tool is available from tupelo.core/destruct. Here is an example:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(let [info {:a 777
:b [2 3 4]}
mania [{:a 11} {:b 22} {:c [7 8 9]}]]
(let [z ::dummy]
(destruct [info {:a z
:b [d e f]}
mania [{:a ?} BBB {:c clutter}]]
(is= z 777)
(is= [d e f] [2 3 4])
(is= a 11)
(is= BBB {:b 22})
(is= clutter [7 8 9])))))
So you can see that inside the destruct expression, the symbols z, d, e, f, BBB, and clutter are given the corresponding values from the input variables info and mania. The special symbol ? is interpreted to mean that the keyword :a creates a symbol a to receive the value 11.

Related

ClojureScript map destructure with defaults filled in?

Does Clojure/Script offer a way to build a destructured map out of the arguments plus filled-in defaults in case the keys weren't supplied in the call?
Consider this example (that doesn't quite do what the code implies by a quick glance). Does clojure provide a way to build the map prompt with these four keys and values either from the caller or the defaults. I hate to think I have to repeat these key names two more times to get what I am after.
(re-frame/reg-event-db
:open-prompt
(fn [db [_ {title :title
text :text
label-yes :label-yes
label-no :label-no
:or {title "Confirm"
text "Are you sure?"
label-yes "Ok"
label-no "Cancel"}
:as prompt}]]
(-> db
(update :state conj :modal-prompt)
(assoc :prompt prompt))))
After reviewing the official documentation page about destructuring, I don't think that Clojure proposes a more convient way of doing that.
But just by curiosity, I was wondering what is the code generated by destructuring, because I'm expecting it relies on macro stuff. Let consider this toy example:
(def my-map {:text "Some text"})
(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt))
;; => "I got Confirm from {:text \"Some text\"}"
(macroexpand '(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt)))
;; => (let*
;; [map__12555
;; my-map
;; map__12555
;; (if
;; (clojure.core/seq? map__12555)
;; (clojure.lang.PersistentHashMap/create
;; (clojure.core/seq map__12555))
;; map__12555)
;; prompt
;; map__12555
;; title
;; (clojure.core/get map__12555 :title "Confirm")]
;; (str "I got " title " from " prompt))
So as you can see, after a macro expansion, the :or mechanism which allows to specifies default value relies on clojure.core/get.
In this particular example, title is affected by (clojure.core/get map__12555 :title "Confirm") form. It's a way to avoid repeating the title variable, but does it worth it?
You can also check the source code of the destructuring macro to get full details about it, but personally I found it pretty difficult to handle ^^'.
it is doable, maybe not very practical though, but nice for self education:
let's begin with making up the function what would be special binding case.
let's say, we want to pass vectors of length 2 or 3, where vector of 2 will represent the simple binding map key-value pair like [:as abc] or [a :a], and the vector of size 3 would be k-v-default triple: [a :a "my default"]. The example of it's usage:
(bindings-preproc [['a 1 "asd"]
['b 2 "ddd"]
[:or {'x 10}]
[:as 'whole]])
resulting to
{a 1, b 2, :or {x 10, a "asd", b "ddd"}, :as whole}
this function could look like this:
(defn bindings-preproc [decls]
(let [defaults (into {} (keep (fn [decl]
(when (and (not (keyword? (first decl)))
(= 3 (count decl)))
(let [[nm _ default] decl]
[nm default])))
decls))
all-kvs (apply assoc {} (mapcat (partial take 2) decls))]
(update all-kvs :or merge defaults)))
(this one doesn't include error checks for the sake of illustrative simplicity)
The next thing is to employ it inside the binding macros. The idea to make bindings-preproc a macro fails, because binding forms are checked for validity before the inner macros are evaluated.
But still we have a feature, that would help, namely reader tags. They are used for example when you use #inst syntax. Since these reader tags are processed at read-time, before any macros are getting expanded, we can plug our preprocessor in.
(here i will use actual reference update, to demonstrate it from repl, but in real projects you would declare these tags in a special file)
user> (alter-var-root
#'default-data-readers
assoc 'my/reader #'user/bindings-preproc)
;;=> {uuid #'clojure.uuid/default-uuid-reader,
;; inst #'clojure.instant/read-instant-date,
;; my/reader #'user/bindings-preproc}
so, now we can try to make it work:
(defn f [#my/reader [[a :a 10]
[b :b 20]
[z :z]
[:keys [k1 k2 k3]]
[[c1 c2 & cs] :c]
[:or {z 101
k3 :wooo}]
[:as whole]]]
{:a a :b b :c1 c1 :c2 c2 :cs cs :z z :k1 k1 :k2 k2 :k3 k3 :whole whole})
user> (f {:a 1000 :c [:one]})
;;=> {:cs nil,
;; :c2 nil,
;; :z 101,
;; :c1 :one,
;; :k3 :wooo,
;; :b 20,
;; :whole {:a 1000, :c [:one]},
;; :k1 nil,
;; :k2 nil,
;; :a 1000}
user> (let [a 10
b 20
#my/reader [[x :x 1]
[y :y 2]
[z :z 100]] {:z 432}]
[a b x y z])
;;=> [10 20 1 2 432]
I like to make a map of all default values, then use into or similar to fuse the user-supplied values into the map of default values. For example:
(ns tst.demo.core
(:use tupelo.core tupelo.test) )
(def stuff-default {:a 1 :b 2})
(defn apply-defaults
[arg]
(let [stuff (glue stuff-default arg)] ; or use `into`. Last one wins, so put defaults first
(with-map-vals stuff [a b]
(newline)
(spyx a)
(spyx b))
stuff))
(dotest
(is= (apply-defaults {}) ; no inputs => all default values
{:a 1, :b 2})
(is= (apply-defaults {:a 100}) ; some inputs => partial defaults
{:a 100, :b 2})
(is= (apply-defaults {:a 100, :b 200}) ; all inputs => no defaults used
{:a 100, :b 200}))
Here glue is like into but with more error checking. We also use tupelo.core/with-map-vals to destruct the map, with less repetition than native Clojure destructuring (vals->map does the reverse).
The output is:
-------------------------------
Clojure 1.10.1 Java 14
-------------------------------
a => 1
b => 2
a => 100
b => 2
a => 100
b => 200
Ran 2 tests containing 3 assertions.
0 failures, 0 errors.

Clojure Null Pointer Exception Due to Function Key Mismatch

I am working through the beginning of a Clojure for the Brave and True example:
(ns the-divine-cheese-code.visualization.svg
(:require [clojure.string :as s])
(:refer-clojure :exclude [min max]))
(defn comparator-over-maps
[comparison-fn ks]
(fn [maps]
(zipmap ks
(map (fn [k] (apply comparison-fn (map k maps)))
ks))))
(def min (comparator-over-maps clojure.core/min [:lat :lng]))
(def max (comparator-over-maps clojure.core/max [:lat :lng]))
I am getting a Null Pointer Exception, though, when I try to run the following code in a CIDER REPL:
(min [{:a 1 :b 3} {:a 5 :b 0}])
I am trying to identify the source of the error within the code. Any help would certainly be appreciated.
The function comparator-over-maps uses the vector of keywords that you pass it to look up values in the map. In this case the maps you're passing have keys :a and :b, but your definition of min is requesting the keys :lat and :lng, which don't exist -- so it receives nil, which is the cause of the NPE. If you change one or the other set of keywords to match, then the example should work, e.g.:
(min [{:lat 1 :lng 3} {:lat 5 :lng 0}])
Based on #BlackBear's comment, I reran the code in the CIDER REPL as:
(min [{:lat 1 :lng 3} {:lat 5 :lng 0}])
and it produced the correct answer:
=> {:lat 1, :lng 0}
Thanks for your help!

How to merge maps and get a map of lists?

Let's say we a list of maps. Maps all have the same keywords, but we don't know the keywords beforehand.
[{:a 1 :b 2} {:a 3 :b 4}]
And what would be the idiomatic way of merging this list into such a map:
{:a [1 3]
:b [2 4]}
Doesn't seem hard, however as I start to implement the function, it gets super ugly and repetitive. I have a feeling that there are much cleaner ways of achieving this.
Thank you
You can actually get a pretty elegant solution by using several functions from the standard library:
(defn consolidate [& ms]
(apply merge-with conj (zipmap (mapcat keys ms) (repeat [])) ms))
Example:
(consolidate {:a 1 :b 2} {:a 3 :b 4})
;=> {:a [1 3], :b [2 4]}
One cool thing about this solution is that it works even if the maps have different key sets.
i would rather use double reduction to "merge" them with update:
(defn merge-maps-with-vec [maps]
(reduce (partial reduce-kv #(update %1 %2 (fnil conj []) %3))
{} maps))
user> (merge-maps-with-vec [{:a 1 :b 2} {:a 3 :b 4 :c 10}])
{:a [1 3], :b [2 4], :c [10]}
It is not as expressive as #Sam Estep's answer, but on the other hand it doesn't generate any intermediate sequences (like every-key-to-empty-vector map which also needs one extra pass through every entry of every map). Of course, premature optimizations are bad in general, but it won't hurt here i guess. Though the reduce based solution looks a bit more obscure, but being put into a library with proper docs it would not look as obscure to the end user (or to yourself a year after)
While many solutions are possible, here is one that uses some of the convenience functions in the Tupelo library:
(ns clj.core
(:use tupelo.core)
(:require [tupelo.schema :as ts]
[schema.core :as s] ))
(s/defn gather-keys
[list-of-maps :- [ts/KeyMap]]
(newline)
(let [keys-vec (keys (first list-of-maps))]
(s/validate [s/Keyword] keys-vec) ; verify it is a vector of keywords
(apply glue
(for [curr-key keys-vec]
{curr-key (forv [curr-map list-of-maps]
(get curr-map curr-key))} ))))
(deftest t-maps
(spyx
(gather-keys [{:a 1 :b 2}
{:a 3 :b 4} ] )))
(gather-keys [{:a 1, :b 2} {:a 3, :b 4}]) ;=> {:a [1 3], :b [2 4]}
Note that this solution assumes that each input map has an identical set of keys. Normally I'd want to enforce that assumption with a sanity check in the code as well.
Looking at the answer from Sam, I would rewrite it with some temporary variables to help document the sub-steps:
(defn consolidate-keys [list-of-maps]
(let [keys-set (set (mapcat keys list-of-maps))
base-result (zipmap keys-set (repeat [] )) ]
(apply merge-with conj base-result list-of-maps)))
(consolidate-keys [ {:a 1 :b 2}
{:a 3 :z 9} ] )
;=> {:z [9], :b [2], :a [1 3]}

Macro to pass patterns and results to core.match/match as a vector

I am struggling on how to construct a macro that lets me pass patterns and results to core.match/match in the form of a vector. I would like to be able to do this:
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
> :low
I have tried the below and several other approaches which do not work, unless I pass patterns as a literal.
(defmacro my-match [e ems]
`(m/match [~e] ~#ems))
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
=> CompilerException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(*cider-repl kontrakt*:106:10)
(let [x {:a 1}]
(my-match x [[{:a 2}] :high
[{:a 1}] :low]))
=> :low
Macros are expanded at compile time, so you cannot rely on runtime information (the value of a parameter) during expansion. The root problem is that you can't apply a macro in the same way you can apply a function.
In clojure, how to apply a macro to a list?
So you have to either resort to using eval:
(defmacro functionize [macro]
`(fn [& args#] (eval (cons '~macro args#))))
(defmacro my-match [e ems]
`(apply (functionize m/match) [~e] ~ems))
Or approach the problem in a different way (do runtime pattern matching instead of compile time pattern matching).
The simplest way to solve your problem is with a plain old map:
(ns clj.core
(:use tupelo.core))
(def x {:a 1} )
(def patterns { {:a 2} :high
{:a 1} :low } )
(spyx (get patterns x))
;=> (get patterns x) => :low
Since you have no "wildcard values", you don't need core.match at all. If you would like to match on wild-card values, please see the function wild-match? in the Tupelo library. Samples:
(wild-match? {:a :* :b 2}
{:a 1 :b 2}) ;=> true
(wild-match? [1 :* 3]
[1 2 3]
[1 9 3] )) ;=> true
(wild-match? {:a :* :b 2}
{:a [1 2 3] :b 2}) ;=> true

Why does Clojure's map destructuring look reversed?

Destructuring a map looks reversed to me. Can anybody explain what is happening?
I expect that this is the right form of destructuring a map
;=> (let [{:a a :b b} {:a 1 :b 2}] [a b])
which returns Exception Unsupported binding form: :a clojure.core/destructure/pb--4541 (core.clj:4029). Clojure documentations say that below is the right way. But it looks that keys and values are reversed.
This should be the right way:
;=> (let [{a :a b :b} {:a 1 :b 2}] [a b])
[1 2]
What is happening when destructuring a map?
It is not really reversed, actually it makes sense. It says: bind to symbol 'a' to value that is associated with the keyword :a
Are you aware of this when your map uses keywords as keys?
(let [{:keys [a b]} {:a 1 :b 2}] [a b])
Much neater and elegant!
Other variants exist if your keys are symbols or strings.
Also, it makes it possible to distinguish between these to cases:
cljs.user=> (let [{foo :foo :as bar} {:foo 3 :as 4}] [foo bar])
[3 {:foo 3, :as 4}]
cljs.user=> (let [{foo :foo bar :as} {:foo 3 :as 4}] [foo bar])
[3 4]