/ / Clojure: ako sa vyhnúť hodnoteniu nenájdeného výrazu v mape get - clojure

Clojure: ako sa vyhnúť zhodnoteniu nezaradeného výrazu v mape

Mám zavolanú mapu robots. Pri vyhľadávaní na mape, ak robot podľa daného kľúča neexistuje, chcem ho vytvoriť. clojure.core/get s not-found arg sa javí ako presne to, čo potrebujem.

Zdá sa však, že not-found výraz sa horlivo hodnotí. Ako môžem zabrániť tomuto vyhodnoteniu, aby som vytvoril nového robota, iba ak žiadny neexistuje?

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"}

odpovede:

3 pre odpoveď č. 1

Plne s tym suhlasim Odpoveď Timothy Dean, ale jeho implementácia nebude fungovať, ak máte na svojej mape nepravdivé alebo nulové hodnoty.

Tu je presnejšie riešenie:

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

A efektné makro:

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

Používam let aby sa vylúčila možnosť spôsobiť dvakrát vedľajší účinok, napr .:

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

4 pre odpoveď č. 2

Problém je v tom, že nejde o rozvetvujúci príkaz - v skutočnosti očakáva hodnotu, nie formu alebo funkciu, v not-found slot. Môžete teda použiť vetviaci príkaz:

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

Ak je to príliš veľa na písanie, môžete získať chuť:

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

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

Po tipoch z komentárov A.Webba sa dostávame k nasledujúcemu makru. Používa rovnakú metódu ako vyššie a dokáže spracovať aj mapy obsahujúce false a nil hodnoty. Je to tiež rýchlejšie ako volanie oboch contains? a get na mape. Namiesto použitia falošnej návratovej hodnoty (false / nil), ktorá vás upozorní na to, že položka nie je prítomná, použite vstavanú funkcionalitu get a vyhľadať niečo, čo by sa na „divokej“ mape nikdy nestalo.

(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

Môžete sa vyhnúť makro a ísť funkčnou cestou:

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

Na mojom stroji je to o niečo pomalšie.