You are getting confused about the distinction between runtime and compile-time, and between macros and functions. Solving a macro problem with eval
is never1 the right answer: instead, make sure that you return code that does what you want to happen. Here's a minimal change to make your original version work.
The main changes are:
defn-from
is a function, not a macro - you just want a convenient way to create lists, which the main macro is responsible for inserting into the result form. You do not want a macro here, because you don't want it expanded into the body of make-placeholders
.
make-placeholders
starts with a do
, and does its for
outside of a syntax-quote. This is the most important part: you want the code returned to the user to look like (do (defn ...))
, as if they'd typed it all in by hand - not (for ...)
, which could only ever def a single function.
(defn defn-from [str mdata args & body]
`(defn ~(symbol str) ~mdata ~args ~@body))
; use list comprehension to generate n functions
(defmacro make-placeholders [n]
(cons `do
(for [i (range 0 n)]
(defn-from (str "_" i) {:placeholder true}
'[& args]
`(nth ~'args ~i)))))
user> (macroexpand-1 '(make-placeholders 3))
(do (clojure.core/defn _0 {:placeholder true} [& args] (clojure.core/nth args 0))
(clojure.core/defn _1 {:placeholder true} [& args] (clojure.core/nth args 1))
(clojure.core/defn _2 {:placeholder true} [& args] (clojure.core/nth args 2)))
1 Very, very rarely
Edit
You can also do this completely without macros, by using a function to create functions and using the lower-level operation intern
instead of def
. It turns out to be much simpler, in fact:
(letfn [(placeholder [n]
(fn [& args]
(nth args n)))]
(doseq [i (range 5)]
(intern *ns* (symbol (str "_" i))
(placeholder i))))
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…