Reputation: 11
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
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 def
s 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
Reputation: 26446
Macros
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 def
ine the labels.
Upvotes: 1