Clojure macro for function generation

I am trying to write a macro that will generate n functions. Here is what I still have:

; only defined this because if I inline this into make-placeholders ; it unable to expand i# in ~(symbol (str "_" i#)) (defmacro defn-from [str mdata args & body] `(defn ~(symbol str) ~mdata ~args ~@body )) ; use list comprehension to generate n functions (defmacro make-placeholders [n] `(for [i# (range 0 ~n)] (defn-from (str "_" i#) {:placeholder true} [& args] (nth args i#)))) ; expand functions _0 ... _9 (make-placeholders 9) 

The error I get is:

 java.lang.ClassCastException: clojure.lang.Cons cannot be cast to java.lang.String 

And I'm not quite sure what this means, but I have this vague idea that (for ...) does not work the way I think it is inside the macro.

+7
source share
2 answers

You are confused about the difference between runtime and compilation time, as well as between macros and functions. Solving a macroeconomic problem with eval never the 1 correct answer: instead, make sure you return code that does what you want. Here's a minimal change to get your original version working.

Major changes:

  • defn-from is a function, not a macro. You just need a convenient way to create lists, which the main macro is responsible for inserting into the result form. You do not need a macro because you do not want it to be expanded into the body of make-placeholders .

  • make-placeholders begins with do and makes it for outside the syntax quote. This is the most important part: you want the code returned to the user to look like (do (defn ...)) , as if he had typed all this manually - not (for ...) , which could only allow only one 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, using a function to create functions and use the lower-level operation intern instead of def . This actually turns out to be much simpler:

 (letfn [(placeholder [n] (fn [& args] (nth args n)))] (doseq [i (range 5)] (intern *ns* (symbol (str "_" i)) (placeholder i)))) 
+17
source

Macro expansion after expression (make-placeholders 9) I get the following:

 (for [i__1862__auto__ (range 0 9)] (defn-from (str "_" i__1862__auto__) {:placeholder true} [& args] (nth args i__1862__auto__))) 

So defn-from expects the string as the first argument, but since it is a macro, (str "_" i__1862__auto__) not evaluated, and thus passes as a list.

I played with this for a while and came up with this:

 (defmacro make-placeholders [n] `(map eval '~(for [cntr (range 0 n)] `(defn ~(symbol (str "_" cntr)) {:placeholder true} [& args] (nth args ~cntr))))) 

Macroexpanding (make-placeholders 3) gives

 (map eval '((defn _0 {:placeholder true} [& args] (nth args 0)) (defn _1 {:placeholder true} [& args] (nth args 1)) (defn _2 {:placeholder true} [& args] (nth args 2)))) 

as I expected, and evaluating this, defines the functions _0 , _1 and _2 :

 ;=> (_0 1 2 3) 1 ;=> (_1 1 2 3) 2 ;=> (_2 1 2 3) 3 

Ok, this works, but I'm still not sure if this is a good idea.

At first, eval is evil. Well, this could also be done without eval , but use do instead (replace map eval in my solution with do ). But you may be making it difficult to understand your code because you are creating functions that are not defined anywhere in your code. I remember that when I just started using Clojure, I was looking at some library for a function, and I could not find it. I started thinking about yikes, this guy must have defined a macro somewhere that defines the function I'm looking for, how will I ever understand what is happening? If this is how people use Clojure, then it will be one hell of a mess and a dwarf that all people talked about Perl ... It turned out that I was just looking for the wrong version, but you can be configured and others for some difficulties.

Having said that, there may be suitable applications for this. Or you can use something like this to generate code and put it in some file (then the source code will be available for verification). Maybe someone more experienced can call back?

+3
source

All Articles