Reputation: 260
This macro
(defmacro readn
[n]
`(list ~@(repeat n '(read))))
works as I expect if I call it e.g. with
(readn 2) ;; expands to (list (read) (read))
However if I call this macro within a let, it does not work anymore:
(let [N 2] (readn N))
;; CompilerException java.lang.ClassCastException:
;; clojure.lang.Symbol cannot be cast to java.lang.Number
I have already tried ,n
'n
and ~n
but this led to no success. I think this is because the N
is defined at run-time and the macro works at compile-time?
Putting it in eval like suggested here yet does work:
(let [N 2] (eval `(readn ~N)))
;; expands to (list (read) (read))
I know I could do something similar using loop, yet loop seems (at least to me) kind of terse.
How do I make this macro work and how do I achieve the sketched behaviour, respectively? Thanks!
Upvotes: 1
Views: 71
Reputation: 13304
I think this is because the
N
is defined at run-time and the macro works at compile-time?
Yes, that's correct. You can think of it this way: when Clojure evaluates a form, it first completely macroexpands the form, then it evaluates that macroexpanded result. Starting with your example:
(let [n 2] (readn n))
Since let
is a macro, Clojure expands it:
(macroexpand-1 '(let [n 2] (readn n)))
;=> (let* [n 2] (readn n))
Now let
has bottomed out to the special form let*
, and the expression for n
(2
) isn't a macro, so Clojure doesn't need to expand it.
However, the body form (readn n)
is a macro form. Remember that macros get passed their arguments unevaluated, so in this case your readn
macro literally receives the symbol n
.
(macroexpand-1 '(readn n))
;=> java.lang.ClassCastException
Macros are just functions that run at compile-time, so this...
(defmacro readn [n]
`(list ~@(repeat n '(read))))
(macroexpand-1 '(readn 2))
;=> (clojure.core/list (read) (read))
(macroexpand-1 '(readn n))
;=> java.lang.ClassCastException
... is equivalent to this:
(defn readn [n]
`(list ~@(repeat n '(read))))
(readn '2)
;=> (clojure.core/list (read) (read))
(readn 'n)
;=> java.lang.ClassCastException
My recommendation would be to just not use a macro at all here. A function would work perfectly well in this case:
(defn readn [n]
(doall (repeatedly n read)))
(let [n 2] (readn n))
Upvotes: 4