Clojure 合并以设置默认值,但可能需要昂贵的函数

Clojure 合并以设置默认值,但可能需要昂贵的函数,clojure,Clojure,在clojure中设置默认值的惯用方法是使用merge: ;; `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"} 但实际上,默认值通常不是简单的常量,而是函数调用。显然,如

在clojure中设置默认值的惯用方法是使用merge:

;; `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))))
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)

(associf my-map
  [:foo :bar] (expensive-func)
  :xyz (other-fn))
并应用我的
sure-*
函数,如
(>msg-sure-uuid-sure-xyz)

有什么更惯用的方法可以做到这一点?我的想法是:

(defn ensure-uuid [msg]
  (if (:uuid msg)
    msg
    (assoc msg :uuid (random-uuid))))
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)

(associf my-map
  [:foo :bar] (expensive-func)
  :xyz (other-fn))
你可以结合使用

然后可以像下面这样合并默认值

(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
       {:foo "custom-value" :uuid "abc"})
并使用

(force (:foo ...))

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)

这是一个好主意,但是在整个应用程序代码中调用
force
会让我感到痛苦,这是我想要避免的。我写了一个宏,它非常适合我的用例。干杯,谢谢。不同的语义,但是-您的解决方案在映射中没有提供值时进行计算,而延迟仅在值最终被使用时进行计算。