Idiomatic clojure map lookup by keyword

后端 未结 4 860
忘了有多久
忘了有多久 2020-11-29 08:09

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 u

相关标签:
4条回答
  • 2020-11-29 08:33

    I would say either is idiomatic. The only caveat is that the second form only works with keywords. Which, I assume by being a deliberate design choice, would give it more reason to be idiomatic.

    0 讨论(0)
  • 2020-11-29 08:36

    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)

    Arguments for (:key map)

    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)

    Arguments for (map :key)

    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)
    

    Arguments for (get map :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/

    0 讨论(0)
  • 2020-11-29 08:39

    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)
      
    0 讨论(0)
  • 2020-11-29 08:54

    (: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."

    0 讨论(0)
提交回复
热议问题