哈希映射的Clojure DRY模式?

时间:2017-07-13 13:39:53

标签: clojure dry

我在let块中进行了大量计算,返回包含数据的哈希映射。以下是一个不那么简单的例子:

(def ground-truth
(let [n              201
      t1             2.0
      linspace       (fn [a b n] (let [d (/ (- b a) (dec n))]
                                   (map (fn [x] (+ a (* x d))) (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]
    {:n n, :times times, :f f, :dt dt, :h-true h-true,
     :h-dot-true h-dot-true, :baro-bias-true baro-bias-true}))

我想要做的是摆脱最终表达中的重复。对于这个小例子来说,这不是一个大问题,但我有一些更长,更复杂,重复使得修改表达式变得乏味且容易出错。

我试过这个宏:

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

仅在eval有效时有效,适用于vars

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

{:foo 41,:bar 42}

但不在let块上:

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

CompilerException java.lang.RuntimeException:无法解析符号:a在此上下文中,编译:(null:1:1)                Util.java:221 clojure.lang.Util / runtimeException
               core.clj:3105 clojure.core $ eval / invokeStatic

在审核了以下SO问题之后,正如预期的那样:Variable scope + eval in Clojureeval a list into a let on clojure

有人可能会说"好吧,您可以在let块之外重复def在名称空间中使用变量,然后使用类似hashup的内容,或者您​​可以将你的重复放在let块的底部,忘掉宏观魔法。但在这个确切的用例中,没有办法不要重复自己

我是否错过了干掉这种代码的好方法?

3 个答案:

答案 0 :(得分:8)

试试这个:

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

然后:

(->hash a b c) => {:a a :b b :c c}

它也适用于let块。

答案 1 :(得分:2)

尝试一下:

(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 :(得分:1)

你可以做flatland.useful.map/keyed所做的事情:在编译时生成地图结构,而不是让调用者生成一个键向量和一个值向量,然后在它们上面调用zipmap。如果您不关心能够构建像字符串或符号键入的地图,那么同一事物的更简单版本将是:

(defmacro keyed [ks]
  (into {}
    (for [k ks]
      [(keyword k) k])))

(let [x 1, y 2]
  (keyed [x y]))

; {:x 1, :y 2}

请注意,有用选择将事物包装在“不必要的”向量中,以便与{:keys [x y]}解构形式对称,并且如上所述还提供了:strs:syms类似物。