drRobertz
drRobertz

Reputation: 3638

Using a macro for creating defrecord methods gives Don't know how to create ISeq from: clojure.lang.Symbol

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

Answers (1)

cgrand
cgrand

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

Related Questions