mac
mac

Reputation: 10075

How to use defmacro instead of eval?

I have come up with the below function, which works as intended but it uses eval which is horrible, and does not exist in ClojureScript where i intend to use it.

(defn path [d p]
  (eval 
    (concat '[-> d] 
      (flatten (map 
                 #(conj (repeat (dec %) 'z/right) 'z/down) 
                 (path-to-vector p))))))

How would I convert it to a macro? My attempt looks like this:

(defmacro path [d p]
  `(concat (-> ~d)
      (flatten
       (map #(conj (repeat (dec %) z/right) z/down)
             (path-to-vector ~p)))))

But that clearly does not work.

Upvotes: 2

Views: 210

Answers (2)

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

Ankur is correct that this is a case for reduce and neither a macro or eval is appropriate, though it's perhaps still worth explaining the mechanics of writing a macro such as this for it's own sake:

Your macro example is very close, all you need is the splicing-unquote function to make it work:

(defmacro path [d p]
  `(-> ~d 
      ~@(flatten
         (map #(conj (repeat (dec %) z/right) z/down)
               (path-to-vector ~p)))))

this evaluates the call to flatten at macro expansion time and then concatenates it into the resulting s-expression/list.

Upvotes: 1

Ankur
Ankur

Reputation: 33637

No need for a macro or eval, the operation is just a reduce:

(defn path [d p]
  (reduce (fn [s v]
            (reduce #(%2 %1) s (conj (repeat (dec v) z/right) z/down)))
          d (path-to-vector p)))

Also note that (conj (repeat (dec %) z/right) z/down) means z/down and then all the z/right coz repeate return a sequence, in case you want all z/right first and last item should be z/down then you should use (conj (vec (repeat (dec %)) z/right) z/down)

Upvotes: 4

Related Questions