2015-04-25 2 views
2

Идиоматического способу установить значения по умолчанию в 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)) 

ответ

-1

Я просто адаптировали 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 
      [email protected](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) 
1

Вы можете использовать delay в сочетании с 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 или что-то в этом роде.

+0

Это хорошая идея, но страдает от последующего вызова 'force' на протяжении всего кода приложения, которое я Мне бы хотелось избежать. Я написал макрос, который очень хорошо работает для моего варианта использования. Cheers & Thanks – ClojureMostly

+0

Хорошо. Тем не менее, различная семантика - ваше решение оценивает, не указано ли значение на карте, с задержкой оно только оценивает, будет ли значение использоваться. – bsvingen

+1

Btw, это только что создано: https://github.com/Malabarba/lazy-map-clojure – ClojureMostly

Смежные вопросы