Inverse process of :keys destructuring: construct map from sequence

Viewed 119

The more I write in Clojure, the more I come across the following sort of pattern:

(defn mapkeys [foo bar baz]
   {:foo foo, :bar bar, :baz baz})

In a certain sense, this looks like the inverse process that a destructuring like

(let [{:keys [foo bar baz]}] ... )

would achieve.

Is there a "built-in" way in Clojure to achieve something similar to the above mapkeys (mapping name to keyword=>value) - perhaps for an arbitrary length list of names?

2 Answers

No such thing is built in, because it doesn't need to be. Unlike destructuring, which is fairly involved, constructing maps is very simple in Clojure, and so fancy ways of doing it are left for ordinary libraries. For example, I long ago wrote flatland.useful.map/keyed, which mirrors the three modes of map destructuring:

(let [transforms {:keys keyword
                  :strs str
                  :syms identity}]
  (defmacro keyed
      "Create a map in which, for each symbol S in vars, (keyword S) is a
  key mapping to the value of S in the current scope. If passed an optional
  :strs or :syms first argument, use strings or symbols as the keys instead."
    ([vars] `(keyed :keys ~vars))
    ([key-type vars]
       (let [transform (comp (partial list `quote)
                             (transforms key-type))]
         (into {} (map (juxt transform identity) vars))))))

But if you only care about keywords, and don't demand a docstring, it could be much shorter:

(defmacro keyed [names]
  (into {}
        (for [n names]
          [(keyword n) n])))

I find that I quite frequently want to either construct a map from individual values or destructure a map to retrieve individual values. In the Tupelo Library I have a handy pair of functions for this purpose that I use all the time:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(dotest
  (let [m {:a 1 :b 2 :c 3}]
    (with-map-vals m [a b c]
      (spyx a)
      (spyx b)
      (spyx c)
      (spyx (vals->map a b c)))))

with result

; destructure a map into values
a => 1
b => 2
c => 3

; construct a map
(vals->map a b c) => {:a 1, :b 2, :c 3}

P.S. Of course I know you can destructure with the :keys syntax, but it always seemed a bit non-intuitive to me.

Related