tagivens
tagivens

Reputation: 11

How do you transform a vector of maps into a series of symbols that eval to a particular map?

I am a Clojure newbie using Light Table to learn macros. My goal is to convert a vector of maps into a list of def statements.

I want to transform the following data structure:

(def label-data
  [
   {:label "lbl_first"}
   {:label "lbl_second"}
   {:label "lbl_third"}
   {:label "lbl_fourth"}
  ]
)

...into the following def statements:

  (def L1 {:label "lbl_first"})
  (def L2 {:label "lbl_second"})
  (def L3 {:label "lbl_third"})
  (def L4 {:label "lbl_fourth"})

I know how to create a macro that generates a single def statement:

(defmacro def-label [num]
  (let [ idx (dec num)
         symb (symbol (str "L" idx))
         datum (label-data num)
         syntax `(def ~symb ~datum)]
    syntax))

When I utilize the macro...

(def-label 2)

I can see the symbol generated by the macro successfully resolves to...

L2

Now, I can conceptualize creating a macro that looks like this:

 (defmacro generate-def-statements-from [lbldata]

 )

but I am not understanding how to iterate over the def-label macro 4 times to generate the multiple def statements desired. Can somebody show me the best technique to accomplish this objective?

Thank you for your advice and guidance, beforehand.

Upvotes: 1

Views: 86

Answers (2)

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

Macros turn one expression into one other expression so you need to produce a single expression. This can be accomplished by wrapping the defs in a do

user> (defmacro def-label-data [label-data]
          `(do ~@(map #(list 'def %1 %2) 
                      (map #(symbol (str "L" %))
                                    (range 1 (inc (count label-data))))
                      label-data)))
#'user/def-label-data

user> (macroexpand '(def-label-data [{:label "lbl_first"}
                                     {:label "lbl_second"}
                                     {:label "lbl_third"}
                                     {:label "lbl_fourth"}]))
(do (def L1 {:label "lbl_first"})
    (def L2 {:label "lbl_second"})
    (def L3 {:label "lbl_third"})
    (def L4 {:label "lbl_fourth"}))

This may not be intuative coming from many other languages where defining new top level forms can only happen at the top level. In clojure this is not the case. You can call def at any level, even in another form. Just remember that is produces a top level var.

If you want to do this as a function instead of a macro then you can't use def because it is a special form that treats it's first argument as a symbol without evaluating it. fortunatly intern does the same thing as def except it evaluates it's arguments:

user> (defn def-label-data [label-data]  
         (map #(intern *ns* %1 %2) 
              (map #(symbol (str "L" %)) 
                   (range 1 (inc (count label-data)))) 
              label-data))
#'user/def-label-data

user> (def-label-data [{:label "lbl_first"}
                       {:label "lbl_second"}
                       {:label "lbl_third"}
                       {:label "lbl_fourth"} 
                       {:label "lbl_fifth"}])
(#'user/L1 #'user/L2 #'user/L3 #'user/L4 #'user/L5)
user> L5
{:label "lbl_fifth"}
user> L4
{:label "lbl_fourth"}
user> L3
{:label "lbl_third"}
user> L2
{:label "lbl_second"}
user> L1
{:label "lbl_first"} 

Upvotes: 2

A. Webb
A. Webb

Reputation: 26446

Macros

  1. Don't evaluate their arguments
  2. Do evaluate their return value

So,

 (defmacro generate-def-statements-from [lbldata] 
  `(do ~@(map-indexed 
           (fn [idx itm] `(def ~(symbol (str "L" (inc idx))) ~itm)) 
           lbldata)))

would have to work on a literal expression

(macroexpand 
  '(generate-def-statements-from   
    [ {:label "lbl_first"}
      {:label "lbl_second"}
      {:label "lbl_third"}
      {:label "lbl_fourth"} ]))

;=> (do
;     (def L1 {:label "lbl_first"})
;     (def L2 {:label "lbl_second"})
;     (def L3 {:label "lbl_third"})
;     (def L4 {:label "lbl_fourth"}))

and would evaluate the do form to define the labels.

Upvotes: 1

Related Questions