Got tired of writing maps, so I wrote a macro to make maps from variables for me

So I heard you like macros…
I got tired of writing lots of functions which would all end in putting nicely named variables into maps.
Typical example, assuming that a and b are descriptive names for something:

(let [a 1
      b 2]
  {:a a :b b}) ;; this last part is boring

So I got tired of rewriting, and wrote this little guy

(defmacro vars->map [& vars] 
  (zipmap (map (comp keyword name) vars)
          vars))

And now I can simply

(let [a 1
      b 2]
  (vars->map a b))

Hope its useful for someone else as well. :smile:

4 Likes

I’m curious: would your background happen to be JavaScript?

I ask because I see this “need” crop up a lot on Slack and various people write these macros and they’re nearly all coming from JS.

We wrote one at work too: commons/extensions.clj at master · worldsingles/commons (github.com) but we ended up not using it at all, in 130K+ lines of Clojure, because – despite seeming like a good idea – we’ve never found the need for it in idiomatic Clojure.

As someone who has never done any JS at all (and hopefully never will), I find myself often needing to create these types of maps, for example when doing structured logging, or when providing error responses from an API. Many times I’ve thought of writing that same macro that Magnus wrote, but somehow I never got around to it :smiley:

1 Like

Background is Python, C, and C++. Have been using Clojure for a few years solo now as well.

I wrote it after writing a function that returns the same names as the let-vars, where the second let-var is dependent on the first one like so:

(defn gg [parameter]
  (let [horse (afun parameter)
        chimera (bfun horse parameter)]
    (vars->map horse chimera))

So reformated, this is

(defn gg [parameter]
    {:horse (afun parameter)
     :chimera (bfun (afun parameter) parameter)})

Which makes me unhappy because what if afun is expensive or slow or even worse; has a long name?

1 Like

I guess I look at that and think “What’s wrong with…”

    (defn gg [parameter]
      (let [horse (afun parameter)]
        {:horse horse
         :chimera (bfun horse parameter)}))
2 Likes

I wrote one too, vals->map. There is also an inverse function that is handy, with-map-vals. I use them all the time to save some typing. It is especially handy to quickly package up problem values for use in (throw (ex-info "some error msg" (vals->map x y z)).

Don’t know how that escaped me. It’s nice to feel dumb sometimes.

Personally I think what I’d like is something more like:

(let [a 10]
  {a, :b 20})

Where if you use a binding as a single, not a pair, it expands to a keyword of the binding as key and the value of the binding as the value.

But keep in mind, whole these tricks are a bit faster to type, they are riskier to accidentally refactor the bonding name and break the use of the map everywhere else, since the key changed.

Edit:

Or even something like:

(let [a 10]
  {a a, :b 20})

Where a binding used as key just gets replaced by a keyword of the same name.

1 Like

But what about…

(let [a 10 b 20]
  {a, b})

That’s {10 20} – and how would tell the difference between the intent of {a, b c} and {a b, c} since we’re already allowed to put expressions in hash maps and keys can be “anything”…?

1 Like

IIUC, we cannot use vars->map until every var has been bound to its final value.

ie, (vars->map (+ a 2)) is a non-starter.

If so, vars->map is the inverse sin of LET, obfuscating the functional style.

Good point. This is where it could be nice for the comma to not just be whitespace, since it could disambiguate.

But to be fair, I didn’t think of it as a core feature, I was thinking within the context of some macro. So you could come up with whatever rule. Here I was assuming this macro doesn’t allow the value of the binding to be a key, instead this macro is used to make the var name itself a keyword key.

That’s what I was thinking reading this—how would the compiler ever comprehend which symbols are meant to be kv tuples, and not an unbalanced/shifted map—but now I’m curious to see someone write macros to respect commas and do exactly this! :laughing: Some preposterous language rewriting like Rewriting the Technical Interview

What you are saying can’t be done using macros… it has to be done through the reader.

It’s just syntatic sugar. I personally like maps the way they are. It gets complicated really quickly if js notation gets imported wholesale. what about a spread operator? what about map defaults?

having said that… a spread operator might be useful for the var-to-map macro.