Clojure: map-values and map-keys

This post covers some useful Clojure functions for transforming the keys and values of maps.

map-values

When writing Clojure, I often find I want to transform the values of a map and apply the same function to each value. Clojure makes this easy as the map function breaks a map data structure into key-value tuples that are easy to manipulate. I find this comes up often enough that having a more specialised function is not only more convenient but conveys the code's intention more clearly.

(defn map-values [f m]
  (->> (map (fn [[k v]] [k (f v)]) m)
       (into {})))

=> (map-values inc {:a 1 :b 2 :c 3})

{:a 2 :b 3 :c 4}

=> (map-values #(str "beta-" %)
    {:sign-up "event" :log-out "event"})

{:sign-up "beta-event" :log-out "beta-event"}

This function is specifically for map collection types, but there is a more generic version called fmap available in the clojure/algo.generic library.

map-keys

Another common task I run into is transforming the keys of a map. Useful when you are at the edge of your codebase and want to communicate to another system that might have different conventions; for example when sending up analytic events. The analytic system might camel case its keys or map keys to entirely different words or domain language.

(defn map-keys [f m]
  (->> (map (fn [[k v]] [(f k) v]) m)
       (into {})))

=> (map-keys #(str "beta-" (name %))
    {:sign-up "event" :log-out "event"})

{"beta-sign-up" "event" "beta-log-out" "event"}

(def keys->analytics-event-names
  {:message-sent         "Primary announcement sent"
   :transaction-complete "Item purchased"})

=> (map-keys keys->analytics-event-names
    {:message-sent         "event"
     :transaction-complete "event"})

{"Primary anouncement sent" "event",
 "Item purchased"           "event"}

There's also a core function called clojure.set/rename-keys which does a similar thing to our map-keys function.

=> (doc clojure.set/rename-keys)

-------------------------
clojure.set/rename-keys
([map kmap])
  Returns the map with the keys in kmap renamed to the vals in kmap

The downside with this function is it takes a key-map not a function, meaning it's only useful for mapping keys. The upside is that it leaves keys that are not in the key-map unchanged.

(def keys->analytics-event-names
  {:message-sent         "Primary announcement sent"
   :transaction-complete "Item purchased"})

=> (map-keys keys->analytics-event-names
    {:message-sent         "event"
     :transaction-complete "event"
     :missing-key          "event"})

{"Primary anouncement sent" "event",
 "Item purchased"           "event"
 nil                        "event"}

=> (clojure.set/rename-keys
    {:message-sent         "event"
     :transaction-complete "event"
     :missing-key          "event"}
    keys->analytics-event-names)

{:missing-key                "event"
 "Primary announcement sent" "event"
 "Item purchased"            "event"}

I hope these functions come in handy.