Say I have a clojure map that uses keywords as its keys:
(def my-car {:color "candy-apple red" :horsepower 450})
I know that I can look up the value associated with the keyword by either using the keyword or the map as a function and the other as its argument:
(my-car :color)
; => "candy-apple red"
(:color my-car)
; => "candy-apple red"
I realize that both forms can come in handy for certain situations, but is one of them considered more idiomatic for straightforward usage like shown above?
(:color my-car)
is fairly standard. There are a few reasons for this, and I won't go into all of them. But here's an example.
Because :color
is a constant, and my-car
is not, hotspot can completely inline the dynamic dispatch of color.invoke(m)
, which it can't do with m.invoke(color)
(in some java pseudo-code).
That gets even better if my-car
happens to sometimes be a record with a color
field instead of a plain map: the clojure compiler can emit code to check "hey, if my-car
is an instance of CarType, then just return my-car.color
; otherwise do all the complicated, slow, hashmap lookup."
From the library coding standards:
Use keyword-first syntax to access properties on objects:
(:property object-like-map)
Use collection-first syntax to extract values from a collection (or use get if the collection might be nil).
(collection-like-map key) (get collection-like-map key)
I put together a list of arguments for and against the two forms. (Edit: Added third option - (get map :key)
which is my new favorite despite being a little bit more verbose)
1) Requested in coding standards
http://dev.clojure.org/display/community/Library+Coding+Standards
2) Still works when map is nil
> (:a nil)
nil
> (nil :a)
ERROR: can't call nil
---counterargument--- if key may be nil, other forms are better
> ({:a "b"} nil)
nil
> (nil {:a "b"})
ERROR: can't call nil
3) Works better for threading and mapping over collections of objects
(-> my-map
:alpha
fn-on-alpha
:beta
fn-on-beta
:gamma
> (def map-collection '({:key "values"} {:key "in"} {:key "collection"}))
> (map :key map-collection)
("values" "in" "collection")
---counterargument--- the code structure of threading is different than usual so different idiomatic tendencies could be applied for map access when needed
4) Potential optimization benefit? (needs verification)
1) Does not throw error when key is non-keyword or nil
> ({:a "b"} nil)
nil
> (nil {:a "b"})
ERROR: can't call nil
> ({"a" "b"} "a")
"b"
> ("a" {"a" "b"})
ERROR: string cannot be cast to IFn
2) Consistency with list access in Clojure
> ([:a :b :c] 1)
:b
> (1 [:a :b :c])
ERROR: long cannot be cast to IFn
3) Similarity to other forms of object access
java> my_obj .alpha .beta .gamma .delta
clj > ((((my-map :alpha) :beta) :gamma) :delta)
clj > (get-in my-map [:alpha :beta :gamma :delta])
cljs> (aget js-obj "alpha" "beta" "gamma" "delta")
4) Alignment when accessesing multiple keys from the same map (separate lines)
> (my-func
(my-map :un)
(my-map :deux)
(my-map :trois)
(my-map :quatre)
(my-map :cinq))
> (my-func
(:un my-map)
(:deux my-map)
(:trois my-map)
(:quatre my-map)
(:cinq my-map))
---counterargument--- alignment worse when accessing same key from multiple maps
> (my-func
(:key map-un)
(:key map-deux)
(:key map-trois)
(:key map-quatre)
(:key map-cinq)
> (my-func
(map-un :key)
(map-deux :key)
(map-trois :key)
(map-quatre :key)
(map-cinq :key)
1) NEVER causes error if arg1 is map/vector/nil and arg2 is key/index/nil
> (get nil :a)
nil
> (get nil nil)
nil
> (get {:a "b"} nil)
nil
> (get {:a "b"} :q)
nil
> (get [:a :b :c] nil)
nil
> (get [:a :b :c] 5)
nil
2) Consistency in form with other Clojure functions
> (get {:a "b"} :a)
:b
> (contains? {:a "b"} :a)
true
> (nth [:a :b :c] 1)
:b
> (conj [:a :b] :c)
[:a :b :c]
3) Alignment benefits of map-first
> (my-func
(get my-map :un)
(get my-map :deux)
(get my-map :trois)
(get my-map :quatre)
(get my-map :cinq))
4) Get-in can be used for nested access with a single call
> (get-in my-map [:alpha :beta :gamma :delta])
> (aget js-obj "alpha" "beta" "gamma" "delta")
Source: testing on http://tryclj.com/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With