Skip to content

Instantly share code, notes, and snippets.

@pjstadig
Created March 6, 2017 16:28
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pjstadig/25c8a775f5403a2f0e3ad94f81ef58ff to your computer and use it in GitHub Desktop.
Save pjstadig/25c8a775f5403a2f0e3ad94f81ef58ff to your computer and use it in GitHub Desktop.
In Clojure you can fetch items from a map three different ways. Which should you use when?
(ns maps.core)
;; In Clojure you can fetch items from a map three different ways. Which should
;; you use when?
(= "bar" ({:foo "bar"} :foo)) ; map as function
(= "bar" (:foo {:foo "bar"})) ; key as function
(= "bar" (get {:foo "bar"} :foo)) ; `get` as function
;; <INCIDENTALLY>
;; Incidentally, all three of these can take a default value to return if the
;; key is not present in the map.
(= "not-here" ({:foo "bar"} :psych "not-here"))
(= "not-here" (:psych {:foo "bar"} "not-here"))
(= "not-here" (get {:foo "bar"} :psych "not-here"))
;; <INCIDENTALLY>
;; Incidentally, using a default may not be what you want; you probably want to
;; use `or`:
(not= "not-here" (:psych {:psych nil} "not-here"))
(= "not-here" (or (:psych {:psych nil}) "not-here"))
;; </INCIDENTALLY>
;; </INCIDENTALLY>
;; There are two factors in choosing how to access items in a map: 1) the nature
;; of the map and/or key, and 2) the semantics of the map and key.
;; == NATURE OF THE MAP AND/OR KEY
;; Could the map be nil? You want to use either 'key as function' or '`get` as
;; function':
(let [m nil]
(m :foo))
;; => NullPointerException ...
;; Could the key be anything other than a keyword? You want to use either 'map
;; as function' or '`get` as function':
(let [k nil]
(k {:foo "bar"}))
;; => NullPointerException ...
(let [k "foo"]
(k {:foo "bar"}))
;; => ClassCastException java.lang.String cannot be cast to clojure.lang.IFn ...
;; Could either the map or the key be nil? Could the key also be not-a-keyword?
;; Use '`get` as function'.
;; == SEMANTICS OF THE MAP AND KEY
;; Is the map a 'function'? For example, is it a transform:
(let [m {"ping" "pong"}]
(get m "ping"))
;; Realizing that it is a function and treating it semantically like a function
;; will help you later when you realize you want to do some more complicated
;; transform, or provide some default. In this case you should use 'map as
;; function':
(let [ping->pong {"ping" "pong"}]
(ping->pong "ping"))
;; Now you can make `ping->pong` into an arbitrary function.
;; Is the key a 'function'? For example, is it accessing data:
(let [k :height-in-cm]
(:height-in-cm {:height-in-cm 180.34}))
;; Realizing that accessing data and calculating data are equivalent (in a
;; purely functional sense) and treating the key as a function will help you
;; later.
(let [height-in-cm (fn [m] (* 2.54 (:height-in-inches m)))]
(height-in-cm {:height-in-inches 71}))
;; Now you can make `height-in-cm` into an arbitrary function.
;; This is function punning, and Clojure allows it in many ways.
;; == CONCLUSION
;; Which method of accessing a map you choose depends on whether the map and/or
;; key could be nil (in which case don't treat either as a function), and
;; whether semantically your code wants a function (in which case treat a map or
;; a keyword as a function, but make sure that your code could take an arbitrary
;; function just as easily).
@joshjones
Copy link

joshjones commented Jun 29, 2017

find is worth mentioning as an often-overlooked but very useful way to look up items in a map when you need to know whether or not there is indeed a key present, even if its value is nil:

(if (first (find {:a 1 :b nil} :b))
  ":b is a key in this map"
  ":b is NOT a key in this map")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment