1

Say I have the following code:

(defmacro test1 [x]
  (list 'fn '[y]
        (if (pos? x)
          '(println y)
          '(println (- y)))))

It does what I need, composes a function based on x, and leaves no references to x. For example, (test1 1) macroexpands into (fn* ([y] (println y))).

Now, I'd like to rewrite it using syntax quoting. This is what I have so far:

(defmacro test2 [x]
  `(fn [y#]
     (if ~(pos? x)
       (println y#)
       (println (- y#)))))

This does exactly the same, with one exception: it leaves an (if true ..) expression in the expanded expression:

(fn* ([y__12353__auto__] (if true (clojure.core/println y__12353__auto__) (clojure.core/println (clojure.core/- y__12353__auto__)))))

This might not be an issue if the compiler can optimize it out. Still, is there a way I could omit it?

Viktor M.
  • 75
  • 5

1 Answers1

2

When you use test2 it will unquote the whole form (pos? x) which will work at compile time if it's a constant number or perhaps a gloabl that is already defined, but not if you pass a lexically scoped variable name that doesn't exist yet.

Thus, you really want this instead:

(defmacro test2 [x]
  `(fn [y#]
     (if (pos? ~x) ; just unquote x, not the whole predicate expression
       (println y#)
       (println (- y#)))))

(macroexpand '(test2 y))
; ==>
; (fn* ([y__1__auto__] 
;   (if (clojure.core/pos? y)
;       (clojure.core/println y__1__auto__) 
;       (clojure.core/println (clojure.core/- y__1__auto__)))))

(defn test-it []
  (let [y -9]
    (test2 y)))

((test-it) 5) ; prints "-5"

Feel free to try this with your version. (hint: You'll get an Exception since clojure.lang.Symbol cannot be cast to java.lang.Number)

UPDATE

Since you want to make the function based on a constant you need to write it a little differently:

(defmacro test3 [x]
  (assert (number? x) "needs to be a compile time number")
  (if (pos? x)
      `(fn [y#] (println y#))
      `(fn [y#] (println (- y#)))))

Now you'll get an error if you use (test3 x) since x is not a number but get what you want when you evaluate (test3 -10) since -10 is a number we can work with compile time. I'm not sure you'll notice a speed improvement though since these are hardly heavy algorithms.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Thanks for the quick answer! About quoting the x symbol, true, this was an error that I overlooked. But still, my goal is to get rid of the conditional in the expanded expression altogether, just like in the test1 macro. I'd like the returned function to be absolutely independent of x or the value of x, for performance reasons (again, this might not be an issue). – Viktor M. Sep 06 '15 at 20:52
  • @ViktorM. I've added a version that removes the `if` run time, but as you see you cannot use it with variables anymore. – Sylwester Sep 06 '15 at 21:38
  • Not being able to use variables, that's a bummer. The algorithms I'd use in the returned functions would be somewhat more complex and would be callled possibly hundreds or thousands of times a second, that's why I'd like to squeeze out as much as possible. Seems like i'll have to stick with simple quoting! – Viktor M. Sep 06 '15 at 22:06
  • @ViktorM. Hope you started your search with [profiling](http://stackoverflow.com/questions/2974916/profiling-tool-for-clojure). – Sylwester Sep 06 '15 at 22:15