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 № 1W 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.