Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Macros by

The following code crashes:

(defmacro m []
  `(fn [x]))

(macroexpand '(m))

The error is:

Call to clojure.core/fn did not conform to spec
:reason "Extra input"

The following code does not crash and works as expected:

(defmacro m []
  `(asdfasdf [x]))

(macroexpand '(m))

How does the first snippet crash when the macro should just be returning a quoted piece of code? Why does it run fn at all?

2 Answers

+3 votes
selected by
Best answer

Not sure what version of Clojure or editor you're using but Clojure 1.10.3 gives more useful output in the standard repl:

user=> (macroexpand '(m))
Syntax error macroexpanding clojure.core/fn at (REPL:1:1).
(user/x) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
user/x - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

As you can see it is not running your code but (recursively) macroexpanding when the problem is encountered. The first expansion will become:

(clojure.core/fn [user/x])

clojure.core/fn is itself a macro that is expanded. During that expansion, the fn macro is checked against the fn spec (this is on by default for macros). The spec finds user/x and sees it as "extra input" in the :params because it does not conform to any expected parameter form (unqualified symbols usually, but also all recursive destructuring forms). Really, the problem here is user/x instead of x.

You can fix this with:

(defmacro m []
  `(fn [~'x]))
reshown by
Much appreciated, that does make sense.
I don't get why x evaluated to user/x when it's syntax quoted though. How is that different from unquoting and then quoting again?
Syntax quote resolves all symbols in terms of the current namespace context. Unquote turns on evaluation, then evaluated a quoted unresolved symbol.
I see thanks again
+1 vote

Try macroexpand-1 instead - then the mistake should become obvious.