7

I'm trying to write a macro that will generate n functions. Here's what I have so far:

; only defined this because if I inline this into make-placeholders
; it's 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 really sure what that means, but I have this vague notion that (for ...) isn't working the way I think it is inside a macro.

2 Answers 2

20

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:

  1. 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.

  2. 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)))

But even better would be to 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:

(doseq [i (range 5)]
  (intern *ns* 
          (-> (symbol (str "_" i))
              (with-meta {:placeholder true}))
          #(nth %& i)))

1 Very, very rarely

Sign up to request clarification or add additional context in comments.

2 Comments

Could you maybe elaborate why do is better here than eval? It seems they are not that different in this particular use case (code would still be evaled at compile time, right?)
If you're going to use eval, there's no reason to get a macro involved at all. You might as well just have a function that maps eval over some code that it generates. But...that's what the compiler does! It takes some input forms and compiles them. Introducing map creates additional complexities: it's lazy. Your snippet will work in the REPL, because the REPL forces the output sequence, but not when compiling a full file. Further, eval has limitations: it doesn't know about enclosing lexical scope, so you can't use closures, etc etc. Eval is a terrible crutch; avoid it if possible.
3

Macroexpanding once the (make-placeholders 9) expression I get this:

(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 a string as first argument, but, because it is a macro, (str "_" i__1862__auto__) is not evaluated and is thus past in as a list.

I played around 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))))

which is what I intended 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, so this works, but I am still not sure its a good idea to do this.

First off eval is evil. Ok, it could also be done without eval, but using do instead (replace map eval in my solution with do). But you are possibly making your code hard to understand because you create functions that are not defined anywhere in your code. I remember that when I just started using Clojure I was looking through some library for a function and I couldn't find it. I started to think yikes, this guy must have defined a macro somewhere that defines the function I am looking for, how am I ever going to understand what's going on? If this is how people are using Clojure then it is going to be one hell of a mess and dwarf everything people have said about Perl... It turned out I was just looking at the wrong version - but you may be setting yourself and others up for some hardship.

Having said this, there may be appropriate uses for this. Or you could use something similar to generate code and put that into some file (then the source code would be available for inspection). Maybe somebody more experienced can chime in?

4 Comments

what you say might be correct, and I am after all fairly new to clojure/lisps, but my understanding was that macros are good for (among other things) code generation. Which is what I'm trying to do. If you look at the hand written functions for what I want, they're very redundant, and seem like a prime candidate for this type of thing. Is this type of thing really considered not a good idea?
Well, they (Lisps) are certainly good at code generation. I think if you are willing to use eval then you could actually get this to work (still thinking about this). It might be possible without eval. But especially in your example, it looks like the only thing the functions will do is to call (nth arg i). Is that really better than using that function directly? In functional programming these functions are not used all that much, see here.
map over eval, nice. Yes, I would like to hear what people think of this. I can see how it might seem dicey.
@Kevin It was a nice exercise ;) I asked if it is a good idea here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.