Search code examples
clojuremacrosvariadic-macros

Using variadic macro arguments as a list


I was trying to write a macro that compares the running times of multiple expressions, and ran into a wall. My problem can be condensed to the following code:

(defmacro test-m [& exprs]
  `(map #(.toString %) ~exprs))

If I call this like (test-m 1 2 3), I would expect this to produce code along the lines of:

(map #(.toString %) [1 2 3])

Which is entirely valid. Unfortunately though, this actually results in an error:

ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn helpers.general-helpers/eval663 (NO_SOURCE_FILE:76)

The only way I could see this possibly happening is if & exprs results in (1 2 3), which would then attempt to call 1 as a function, which is obviously wrong.

How can I map over a variadic argument list in a macro?


Solution

  • I figured it out while writing the question, so I thought I'd post an answer in case anyone else is stuck here in the future.

    The argument list needs to forced into a list representatiom first, or else it will be evaluated as a form. This can be done like:

    (defmacro test-m [& exprs]
      `(map #(.toString %) (list ~@exprs))
    

    Note the exprs are expanded via @, and given to list. (Thanks @amalloy for the correction).

    Obvious is retrospect, but it held me up for a while. Hopefully this helps someone.