3
votes

I'm trying to 'get' clojure macros and write a tweaked version of the are macro in terms of the existing are macro. The tweak I want is to have signature [argv expr args] instead of [argv expr & args] So I've tried

(defmacro are2 [argv expr args] `(clojure.test/are ~arg ~expr ~@args))

which kind of works, except it expects an unquoted list:

(are2 [input] (= 0 input) (1 2 3))

where I'd rather it expect a quoted list:

(are2 [input] (= 0 input) '(1 2 3))

But that results in:

Unable to resolve symbol: quote in this context.

If I try

(are2 [input] (= 0 input) (list 1 2 3))

instead then list itself gets processed as an test case.

What have I not understood / how can I get past the quote in my macro

1
Can show example of form after macroexpand? What do you want to get finally from (are2 [input] (= 0 input) (1 2 3))?Mikita Belahlazau
I want it to 'do whatever the clojure.test/are does but without the & in the sig ;-) here's the code of clojure.test/are: (defmacro are [argv expr & args] (if (or (and (empty? argv) (empty? args)) ;; Catch wrong number of args (and (pos? (count argv)) (pos? (count args)) (zero? (mod (count args) (count argv))))) `(temp/do-template ~argv (is ~expr) ~@args) (throw (IllegalArgumentException. "The number of args doesn't match are's argv."))))Chris F Carroll
But what I was trying to understand is, why is my attempt to re-use are failingChris F Carroll
One more question. Why do you need to pass quote list to macro?Mikita Belahlazau
I don't 'need' to -- I want to understand why it doesn't work. Quoting is the usual way to pass a literal list around? Whereas passing around an unquoted list like (1 2 3) usually gets a Long cannot be cast to IFnChris F Carroll

1 Answers

5
votes

'(1 2 3) is expanding into (quote (1 2 3)) which has an extra quote symbol and one too many levels of list which you can see with macroexpand-1:

user> (macroexpand-1 '(are2 [input] (= 0 input) '(1 2 3)))
(clojure.test/are [input] (= 0 input) quote (1 2 3)) 

you can drop the quote from the list by wrapping it int first and rest

 user> (defmacro are2 [argv expr args] 
          `(clojure.test/are ~argv ~expr ~@(first (rest args))))
#'user/are2
user> (macroexpand-1 '(are2 [input] (= 0 input) '(1 2 3)))
(clojure.test/are [input] (= 0 input) 1 2 3) 

which then runs as a test:

user> (are2 [input] (= 0 input) '(1 2 3)

FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (= 0 1)
  actual: (not (= 0 1))

FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (= 0 2)
  actual: (not (= 0 2))

FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (= 0 3)
  actual: (not (= 0 3))
false