哈希映射的 Clojure DRY 模式?
Clojure DRY pattern for hash-map?
我在 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, compiling:(null:1:1)
Util.java: 221 clojure.lang.Util/runtimeException
core.clj: 3105 clojure.core$eval/invokeStatic
在查看了以下 SO 问题以及许多其他问题后符合预期:Variable scope + eval in Clojure, eval a list into a let on clojure
有人可能会说“好吧,你可以通过 def
在命名空间中设置你的变量然后使用类似 hashup
的东西在你的 let
块之外重复,或者你可以有你在 let
块底部的重复并忘记宏魔法。但是在这个确切的用例中没有办法不要重复自己。
我是不是错过了一个干掉这种代码的好方法?
试试这个:
(defmacro ->hash [& vars]
(list `zipmap
(mapv keyword vars)
(vec vars)))
然后:
(->hash a b c) => {:a a :b b :c c}
它也适用于 let
个块。
您可以做 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}
请注意,有用的选择在此处将内容包装在 "unnecessary" 向量中以与 {:keys [x y]}
解构形式对称,并且如前所述,还提供了 :strs
和 :syms
类似物.
试试那个:
(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}
我在 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, compiling:(null:1:1) Util.java: 221 clojure.lang.Util/runtimeException
core.clj: 3105 clojure.core$eval/invokeStatic
在查看了以下 SO 问题以及许多其他问题后符合预期:Variable scope + eval in Clojure, eval a list into a let on clojure
有人可能会说“好吧,你可以通过 def
在命名空间中设置你的变量然后使用类似 hashup
的东西在你的 let
块之外重复,或者你可以有你在 let
块底部的重复并忘记宏魔法。但是在这个确切的用例中没有办法不要重复自己。
我是不是错过了一个干掉这种代码的好方法?
试试这个:
(defmacro ->hash [& vars]
(list `zipmap
(mapv keyword vars)
(vec vars)))
然后:
(->hash a b c) => {:a a :b b :c c}
它也适用于 let
个块。
您可以做 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}
请注意,有用的选择在此处将内容包装在 "unnecessary" 向量中以与 {:keys [x y]}
解构形式对称,并且如前所述,还提供了 :strs
和 :syms
类似物.
试试那个:
(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}