合并以设置默认值,但可能是昂贵的功能
merge to set default values, but potentially expensive functions
在 clojure 中设置默认值的惯用方法是合并:
;; `merge` can be used to support the setting of default values
(merge {:foo "foo-default" :bar "bar-default"}
{:foo "custom-value"})
;;=> {:foo "custom-value" :bar "bar-default"}
然而在现实中,默认值通常不是简单的常量而是函数调用。显然,如果不打算使用该函数,我想避免调用它。
到目前为止,我正在做类似的事情:
(defn ensure-uuid [msg]
(if (:uuid msg)
msg
(assoc msg :uuid (random-uuid))))
并应用我的 ensure-*
函数,例如 (-> msg ensure-uuid ensure-xyz)
。
执行此操作的更惯用的方法是什么?我在想:
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)
(associf my-map
[:foo :bar] (expensive-func)
:xyz (other-fn))
您可以使用 delay
combined with force
.
然后您可以像这样合并您的默认值
(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
{:foo "custom-value" :uuid "abc"})
并使用
访问值
(force (:foo ...))
或
(force (:uuid ...))
random-uuid
将仅在您实际需要该值时(且仅是第一次)被调用。
您可以将对 force
的调用包装在 get-value
函数或类似的函数中。
我刚刚修改了 condp
宏并编写了以下内容:
(defmacro assoc-if-nil
"Takes a map as the first argument and a succession of key value pairs that
are used to set the key to value if the key of the map is nil. The value part
is only evaluated if the key is nil (thus different semantics to (merge)).
Example:
(assoc-if-nil {:a {:b :set}}
[:a :b] :non-def
[:a :c] :non-def
:d :non-def)
;; =>{:a {:b :set, :c :non-def}, :d :non-def}"
[m & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
get-fn (fn[kork] (if (vector? kork) `get-in `get))
assoc-fn (fn[kork] (if (vector? kork) `assoc-in `assoc))
pstep (fn [[kork v]] `(if-not (~(get-fn kork) ~g ~kork)
(~(assoc-fn kork) ~g ~kork ~v)
~g))]
`(let [~g ~m ;; avoid double evaluation
~@(interleave (repeat g) (map pstep (partition 2 clauses)))]
~g)))
扩展为:
(macroexpand-1 '
(assoc-if-nil m
[:a :b] :nested
:d :just-key))
(clojure.core/let
[G__15391 m
G__15391
(clojure.core/if-not
(clojure.core/get-in G__15391 [:a :b])
(clojure.core/assoc-in G__15391 [:a :b] :nested)
G__15391)
G__15391
(clojure.core/if-not
(clojure.core/get G__15391 :d)
(clojure.core/assoc G__15391 :d :just-key)
G__15391)]
G__15391)
在 clojure 中设置默认值的惯用方法是合并:
;; `merge` can be used to support the setting of default values
(merge {:foo "foo-default" :bar "bar-default"}
{:foo "custom-value"})
;;=> {:foo "custom-value" :bar "bar-default"}
然而在现实中,默认值通常不是简单的常量而是函数调用。显然,如果不打算使用该函数,我想避免调用它。
到目前为止,我正在做类似的事情:
(defn ensure-uuid [msg]
(if (:uuid msg)
msg
(assoc msg :uuid (random-uuid))))
并应用我的 ensure-*
函数,例如 (-> msg ensure-uuid ensure-xyz)
。
执行此操作的更惯用的方法是什么?我在想:
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)
(associf my-map
[:foo :bar] (expensive-func)
:xyz (other-fn))
您可以使用 delay
combined with force
.
然后您可以像这样合并您的默认值
(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
{:foo "custom-value" :uuid "abc"})
并使用
访问值(force (:foo ...))
或
(force (:uuid ...))
random-uuid
将仅在您实际需要该值时(且仅是第一次)被调用。
您可以将对 force
的调用包装在 get-value
函数或类似的函数中。
我刚刚修改了 condp
宏并编写了以下内容:
(defmacro assoc-if-nil
"Takes a map as the first argument and a succession of key value pairs that
are used to set the key to value if the key of the map is nil. The value part
is only evaluated if the key is nil (thus different semantics to (merge)).
Example:
(assoc-if-nil {:a {:b :set}}
[:a :b] :non-def
[:a :c] :non-def
:d :non-def)
;; =>{:a {:b :set, :c :non-def}, :d :non-def}"
[m & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
get-fn (fn[kork] (if (vector? kork) `get-in `get))
assoc-fn (fn[kork] (if (vector? kork) `assoc-in `assoc))
pstep (fn [[kork v]] `(if-not (~(get-fn kork) ~g ~kork)
(~(assoc-fn kork) ~g ~kork ~v)
~g))]
`(let [~g ~m ;; avoid double evaluation
~@(interleave (repeat g) (map pstep (partition 2 clauses)))]
~g)))
扩展为:
(macroexpand-1 '
(assoc-if-nil m
[:a :b] :nested
:d :just-key))
(clojure.core/let
[G__15391 m
G__15391
(clojure.core/if-not
(clojure.core/get-in G__15391 [:a :b])
(clojure.core/assoc-in G__15391 [:a :b] :nested)
G__15391)
G__15391
(clojure.core/if-not
(clojure.core/get G__15391 :d)
(clojure.core/assoc G__15391 :d :just-key)
G__15391)]
G__15391)