Daniel Ribeiro
Daniel Ribeiro

Reputation: 3119

Macros iterating over undefined Symbols

When applying a macro multiple times with a another macro, bare Symbols are not inserted into the current context:

(defmacro ty [type]
  `(deftype ~type []))

(defmacro empties [& args]
  (doseq [arg args]
    `(ty ~arg))
  )

(empties Base Person Animal)
;equivalent to:
;(ty Base)
;(ty Person)
;(ty Animal)


(derive ::Person ::Base)
(derive ::Animal ::Base)
(ty Me)
(prn ::Me)
(prn Me)
(empties Empty)
(prn ::Empty)
(prn Empty)

The last line gives: "Unable to resolve symbol: Empty in this context", even though when using the direct macro ty, it works. Any way to solve this? If possible without eval it would be much better.

Upvotes: 3

Views: 256

Answers (2)

Svante
Svante

Reputation: 51501

(defmacro empties [& args]
  (doseq [arg args]
    `(ty ~arg)))

(empties Base Person Animal)
;equivalent to:
;(ty Base)
;(ty Person)
;(ty Animal)

This is wrong. Your empties call means that the macro expansion function for empties gets as arguments the symbols Base, Person, and Animal. It then evaluates the ty macro call for each, but does not return anything, as doseq always returns nil. So, the expanded code from that empties call is nil. You need to return a single form from your macro function. You should wrap the multiple forms into a do, and actually return all the subforms to that:

(defmacro empties [& args]
  `(do ~@(map (fn [arg]
               `(ty ~arg))
              args)))

Upvotes: 5

amalloy
amalloy

Reputation: 91857

FWIW, I prefer to write @Svante's solution as

(defmacro empties [& args]
  (cons `do
        (for [arg args]
          `(ty ~arg))))

which is also pretty close to the structure of your doseq approach.

Upvotes: 2

Related Questions