Clojure DRY hash map template?

I do a lot of computation in let blocks, returning hash maps containing data. The following is not a very minimal example:

 (def ground-truth (let [n 201 t1 2.0 linspace (fn [abn] (let [d (/ (- ba) (dec n))] (map (fn [x] (+ a (* xd))) (range n)))) times (vec (linspace 0.0, t1, n)) wavelength 1 wavespeed 1 f (* (/ wavespeed wavelength) 2 Math/PI) dt (- (get times 1) (get times 0)) amplitude 5.0 ground-level 10.0 h-true (mapv #(+ ground-level (* amplitude (Math/sin (* f %)))) times) h-dot-true (mapv #(* amplitude f (Math/cos (* f %))) times) baro-bias-true -3.777] {:nn, :times times, :ff, :dt dt, :h-true h-true, :h-dot-true h-dot-true, :baro-bias-true baro-bias-true})) 

What I want to do is get rid of the repetition in the final expression. This is not such a big problem for this small example, but I have some that are much longer and more complicated, and repetition makes modifying the expression tedious and error prone.

I tried this macro:

 (defmacro hashup [name-list] `(into {} (map vector (mapv keyword ~name-list) (mapv eval ~name-list)))) 

which only works if eval works, which works on vars :

 (def foo 41) (def bar 42) (hashup '[foo bar]) 

{: foo 41 ,: bar 42}

but not on let blocks:

 (let [a 1, b (inc a)] (hashup '[ab])) 

CompilerException java.lang.RuntimeException: cannot resolve character: a in this context, compilation: (null: 1: 1) Util.java: 221 clojure.lang.Util / runtimeException
core.clj: 3105 clojure.core $ eval / invokeStatic

as expected after addressing the following SO issues, among many others: Variable region + eval in Clojure , eval list in let on Clojure

You could say: "well, you can either repeat your variables outside the borders of your let blocks on def with namespaces, and then use something like hashup , or you can repeat your repetition at the bottom of let blocks and forgets about macromagy. But in this particular use case, there is no “Do not repeat yourself.”

I missed a good way DRY from this type of code?

+5
source share
3 answers

Try the following:

 (defmacro ->hash [& vars] (list `zipmap (mapv keyword vars) (vec vars))) 

Then:

 (->hash abc) => {:aa :bb :cc} 

And it also works inside let blocks.

+8
source

Try the following:

 (defmacro letm [bindings] `(let* ~(destructure bindings) (merge ~@ (map #(hash-map (keyword %) %) (take-nth 2 bindings))))) (letm [a 1 b (+ 1 2) c (println "As") d (+ b 2) e 'selam]) => As => {:a 1, :b 3, :c nil, :d 5, :e selam} 
+2
source

You can do what flatland.useful.map/keyed : generate a map structure at compile time instead of generating a key vector and a vector of values ​​that call the function, and then call zipmap on them. A simpler version of the same, if you do not care about the possibility of creating cards such as this string using a string or symbol, would be the following:

 (defmacro keyed [ks] (into {} (for [k ks] [(keyword k) k]))) (let [x 1, y 2] (keyed [xy])) ; {:x 1, :y 2} 

Please note that it is useful to choose to wrap things in an “unnecessary” vector here for symmetry with the destructuring form {:keys [xy]} , as well as the mentioned analogues :strs and :syms .

+1
source

Source: https://habr.com/ru/post/1269792/


All Articles