/ / Clojure: jak uniknąć ewaluacji nieznalezionego wyrażenia w map get - clojure

Clojure: jak uniknąć ewaluacji nieznalezionego wyrażenia na mapie get - clojure

Mam mapę o nazwie robots. Patrząc na mapę, jeśli robot przy danym kluczu nie istnieje, chcę go utworzyć. clojure.core/get z not-found Arg wydaje się dokładnie tym, czego potrzebuję.

Wydaje się jednak, że not-found ekspresja jest oceniana chętnie. Jak mogę zapobiec tej ocenie, aby utworzyć nowego robota tylko wtedy, gdy nie istnieje?

robot> (def robots {1 {:name "R2D2"}})
#"robot/robots


robot> (get robots 1)
{:name "R2D2"}

robot> (get robots 1 (println "damn it"))
damn it
{:name "R2D2"}

Odpowiedzi:

3 dla odpowiedzi № 1

W pełni się z tym zgadzam Odpowiedź Timothy Dean, ale jego implementacja nie zadziała, jeśli masz na mapie fałszywe lub zerowe wartości.

Oto dokładniejsze rozwiązanie:

(if (contains? robots 1)
(get robots 1)
(println "Beep boop"))

I fantazyjne makro:

(defmacro get-lazy [map key not-found]
`(let [map# ~map
key# ~key]
(if (contains? map# key#)
(get map# key#)
~not-found)))

Używam let aby wyeliminować możliwość dwukrotnego wywołania efektu ubocznego, np .:

(get-lazy {1 false}
(do (println "Side effect") 1)
(println "Oops!"))

4 dla odpowiedzi nr 2

Problem polega na tym, że nie jest to instrukcja rozgałęziająca - w rzeczywistości oczekuje wartości, a nie formy lub funkcji w not-found otwór. Możesz więc użyć instrukcji rozgałęzienia:

(if-let [r (get robots 1)] r (println "Beep boop"))

Jeśli to za dużo pisania, możesz wymyślić:

(defmacro get-lazy
[map idx statement]
`(if-let [v# (get ~map ~idx)] v# ~statement))

user=> (get-lazy robots 1 (println "Oops!"))
{:name "R2D2"}

Idąc za wskazówkami z komentarzy A.Webb, dochodzimy do następującego makra. Używa tej samej metody jak powyżej, a także może obsługiwać mapy zawierające false i nil wartości. Jest także szybszy niż dzwonienie do obu contains? i get na mapie. Zamiast używać fałszywej wartości zwracanej (false / zero), aby wskazać, że element nie jest obecny, skorzystaj z wbudowanej funkcji get i poszukaj czegoś, co nigdy nie wystąpiłoby na „dzikiej” mapie.

(defmacro get-lazy
[map idx statement]
`(let [r# (get ~map ~idx ~::nil)]
(if (identical? r# ~::nil) ~statement r#)))

user=> (get-lazy {:idx 8} :idx (println "Yo."))
8
user=> (get-lazy {:coconuts :migration} :swallow (println "Who goes there?"))
Who goes there?
user=> (ns another.ns)
another.ns=> (user/get-lazy {:LUE ::nil} :LUE (println "42."))
:another.ns/nil

Możesz ominąć makro i wybrać funkcjonalną trasę:

(let [sentinel ::nil]
(defn lazyget
[map idx function]
(let [r (get map idx sentinel)]
(if (identical? r sentinel) (function) r))))

To trochę wolniej na mojej maszynie.