# Macro that generates a defn

I need to save nested data to disk for a project. Right now, my state is a nested record, and has proven to be a pain to serialize. I tried compressing it to a map, but after testing it, I realized my code failed in a unfortunate corner case that proved it to impractical. I decided to take the suggestion to drop using records altogether since I don't need the speed, but found it annoying to write ->-ish pseudo-constructors for everything I needed to represent. I decided to write a macro that takes a vector of keyword fields, and emits a defn that creates an map with the field names mapped to the arguments of the function:

(defmap Hello [:one :two :three])
(->Hello 1 2 3)

{:one 1, :two 2, :three 3}


I decided for the macro to take keywords as arguments so they're introduced to IntelliJ. defrecord doesn't give any hints, so the first time you use a record's fields, they aren't suggested by IntelliJ.

Unfortunately, IntelliJ complains about Hello not existing, and doesn't recognize ->Hello as a declared function, so this isn't very nice to use, but it was a fun little sidetrack. I don't know if I'll actually use it, but it's the most complicated macro I think I've ever written, and I'd like it reviewed.

I'd like to know if I'm doing anything roundabout or overly complicated. I'd also like to know if there's a way to hint to IntelliJ that Hello doesn't need to be defined, and that ->Hello does.

(defmacro defmap
"Generates a defn that creates a function with the name (str \"->\" map-name) that
returns a map. The map will have keys of fields, with the values of the arguments passed to the function."
[map-name fields]
args# (mapv (comp symbol name)  fields)
body-map# (into {} (map #(do [% (symbol %2)]) fields args#))]

(defn ~(symbol adjusted-name#) ~args# ~body-map#)))

• Why do you want -> constructors? As opposed to literally defining the map (def a-hello {:one 1 :two 2 :three 3})? – BenC Sep 12 '17 at 7:19
• @BenC I noticed the constructor functions I was writing basically just took the parameters of the function, and mapped each parameter to a keyword with the same name. I decided to try automating that. As noted in the question though, I didn't really plan in using the macro in the end. It was more of a quick side project. And I didn't just define a map literal because I may need to change what the map contains. It's handy to have a constructor. I also want to make sure that I don't accidentally introduce I typo into one of the keywords, so I don't want to manually writing them everywhere. – Carcigenicate Sep 12 '17 at 12:35

The code is nice, there are only some minor improvements here.

1. I love that you have written a docstring.

2. You do not need the # in the variable names. These names are used for generating symbols but you do not use this capability in the code.

3. You could make adjusted-make a symbol when defined in the let block. This way the constructed form would be simpler and more readable.

4. I prefer doing assert calls for error handling in macros. You could check that the first parameter is a symbol? and the second is a vector? of keys.

5. Instead of (into {} (map ...)) you can also use zipmap to construct the map.

The code can be rewritten as the following:

  (defmacro defmap
"Generates a defn that creates a function with the name (str \"->\" map-name) that
returns a map. The map will have keys of fields, with the values of the arguments passed to the function."
[map-name fields]
(assert (symbol? map-name))
(assert (vector? fields))
(let [adjusted-name (symbol (str "->" map-name))
args          (mapv (comp symbol name) fields)
body-map      (zipmap fields args)]