Reputation: 3638
Playing around with Clojure, I tried to use macros to create the methods for a protocol in a defrecord
, but am getting type errors I cannot understand.
With this minimal protocol
(defprotocol fooprot
(ffoo [_ v])
)
I would like to use a macro to define the method. Here are two non-working attempts with different quoting:
(defmacro mk-m1
[fnname value]
`(~fnname [~'_ ~'p]
(str ~'p ~value )))
(defmacro mk-m2
[fnname value]
(list fnname '[_ p]
(list 'str 'p value )))
Expanding them gives the same output (with the exception of the namespace and the type of lists)
(macroexpand '(mk-m1 ffoo "foo")) ; => (ffoo [_ p] (clojure.core/str p "foo"))
(macroexpand '(mk-m2 ffoo "foo")) ; => (ffoo [_ p] (str p "foo"))
And if I copy-and-paste that output into a defrecord
they both work: (In this scaled-down example the methods do not use the fields (x
, y
))
(defrecord myrec1 [x y]
fooprot
(ffoo [_ v] (str v "foo"))
)
(defrecord myrec2 [x y]
fooprot
(ffoo [_ p] (clojure.core/str p "foo"))
)
(ffoo (->myrec1 1 2) "hello ") ; => "hello foo"
(ffoo (->myrec2 1 2) "hello ") ; => "hello foo"
Now, when I try to use a macro to define the methods, I get IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol
:
(defrecord myrec-m1 [x y]
fooprot
(mk-m1 ffoo "foo")
)
; => IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol clojure.lang.RT.seqFrom (RT.java:505)
(defrecord myrec-m2 [x y]
fooprot
(mk-m2 ffoo "foo")
)
; => IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol clojure.lang.RT.seqFrom (RT.java:505)
... which is where I'm lost. Which Symbol
is the problem? Looking at
what the macro expands to, it seems reasonable to me:
(let [ex (macroexpand '(mk-m1 ffoo "foo"))]
(println ex " : " (type ex))
(doseq [tmp ex] (println tmp " : " (type tmp))))
; => (ffoo [_ p] (clojure.core/str p foo)) : clojure.lang.Cons
; ffoo : clojure.lang.Symbol
; [_ p] : clojure.lang.PersistentVector
; (clojure.core/str p foo) : clojure.lang.Cons
; nil
and
(let [ex (macroexpand '(mk-m2 ffoo "foo"))]
(println ex " : " (type ex))
(doseq [tmp ex]
(println tmp " : " (type tmp))) )
; => (ffoo [_ p] (str p foo)) : clojure.lang.PersistentList
; ffoo : clojure.lang.Symbol
; [_ p] : clojure.lang.PersistentVector
; (str p foo) : clojure.lang.PersistentList
; nil
where the vector elements are also Symbols:
(doseq [e (nth (macroexpand '(mk-m1 ffoo "foo"))1)]
(println e " : " (type e)))
;=> _ : clojure.lang.Symbol
; p : clojure.lang.Symbol
; nil
What am I missing? Am I overlooking something trivial or is there some interaction with the defrecord
macro?
A working work-around is to instead make the macros create functions and then call those generated functions in the methods as
(defrecord myrec1 [x y]
fooprot
(ffoo [this v] (generated-ffoo this v))
)
which is OK, but I want to understand this.
Upvotes: 0
Views: 262
Reputation: 7949
defrecord
is a macro itself and as such does not perform macroexpansion on its child forms.
A quick way to circumvent this is to define your own my-defrecord
that expands to a defrecord
with your desired method impls.
Upvotes: 3