【问题标题】:Clojure DRY pattern for hash-map?哈希映射的 Clojure DRY 模式?
【发布时间】:2017-12-18 08:09:21
【问题描述】:

我在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: Unable to resolve symbol: a in this context, compile:(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

有人可能会说“好吧,您可以通过deffing 命名空间中的变量然后使用hashup 之类的东西在let 块之外进行重复,或者您可以在@ 的底部进行重复987654335@ 阻止并忘记宏魔法。但是在这个确切的用例中,没有办法不要重复自己

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

【问题讨论】:

  • 不是答案,但您可能想看看fnk from github.com/plumatic/plumbing
  • 除了 Wout Neirynck 的好回答之外,还有一点建议:您自己尝试宏的问题在于您将其视为常规函数,即您正在尝试直接创建向量。宏不应尝试创建最终结果,而应创建一个表达式,该表达式将在评估时创建最终结果。由于您正在创建一个随后将被评估的表达式,因此您无需显式使用 eval
  • @Rörd 从您的解释中,我想我现在对宏何时进行评估有了更好的理解:我们首先重写代码而不进行评估,然后在运行时进行评估。这与 Wout 下面的回答一致,其中 vars 从未被评估过。在每个宏前面加上反引号以暂停评估的反射会引起分心:它暂停的评估是重写时评估,不应与运行时评估混为一谈。

标签: clojure dry


【解决方案1】:

试试这个:

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

然后:

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

它也适用于 let 块。

【讨论】:

  • 这里一定要使用 s 反引号,而不是普通的引号。如果调用者的命名空间没有将zipmap 解析为clojure.core/zipmap'zipmap 将无法工作。
【解决方案2】:

您可以做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 类似物。

【讨论】:

    【解决方案3】:

    试试那个:

    (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}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多