Brian
Brian

Reputation: 129

clojure macro eval

Being drawn to and new to clojure and its macros I set myself the task of writing a macro that generates all strings of length 'n' from a list of chars such as "abc". So for n=2 the output should be "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc". I started with the following function as a template: (defn mkstr [n v] (for [i v j v] (str i j))). The repetition of 'v' in the for binding and the creation of how many vars should be a function of 'n'; in this specific case: 2.

After reading about 'quote' 'unquote' etc., then following an excellent online tutorial about macros, much trial and error, and some plain luck I managed to produce the following function and macro, which give the desired output whatever the value of 'n'. The really hard part was generating the variable amount of code needed in the 'for' bindings.

(defn mkvars [n] 
"Gives a list of 'n' unique symbols"
(let [vc (repeatedly n #(gensym ))] vc)) 

(defmacro mkcoms [n syms] 
"Generates a list of possible combinations of length 'n' from a string of symbols"
`(let [vs# (mkvars ~n) sy# ~syms  
    forarg# (vec (interleave vs# (repeat ~n sy#)))]
     `(for ~forarg# (str ~@vs#))))

Now my 'real' problem or lack of understanding is that to obtain the output I must do this: (eval (mkcoms len chars)). Why does this only work by using 'eval'? True, it is usable as is, but something feels wrong about it.

Upvotes: 2

Views: 917

Answers (1)

Kyle Burton
Kyle Burton

Reputation: 27528

Your macros is returning a quoted form, which is why it works when you pass it to eval. I'm not understanding what the purpose of the macro is over the function, so I hope this explanation is what you're after.

A macro is supposed to generate the code it represents and return it. Your macro generates a quoted form. If you remove the outer layer of back-quoting, which looks like the intention is to be part of the code doing the expansion (the macro) and not the resultant code, you do get the execution at macro-expansion-time:

(defmacro mkcoms [n syms]                                                                                                      
  "Generates a list of possible combinations of length 'n' from a string of symbols"                                           
  (let [vs     (mkvars n)                                                                                                      
        sy     syms                                                                                                            
        forarg (vec (interleave vs (repeat n sy)))]                                                                            
    `(for ~forarg (str ~@vs))))

This sounds like what you're after, though I admit I don't understand why you want this to happen at 'compile time' vs run-time.

Upvotes: 2

Related Questions