Search code examples
clojureprotocolsfrequencycensus

How do I get core clojure functions to work with my defrecords


I have a defrecord called a bag. It behaves like a list of item to count. This is sometimes called a frequency or a census. I want to be able to do the following

(def b (bag/create [:k 1 :k2 3])  
(keys bag)
=> (:k :k1)

I tried the following:

(defrecord MapBag [state]                                                                                                                                         
  Bag                                                                                                                                                             
   (put-n [self item n]                                                                                                                                          
     (let [new-n (+ n (count self item))]                                                                                                                        
          (MapBag. (assoc state item new-n))))                                                                                                                      

  ;... some stuff

  java.util.Map                                                                                                                                                   
    (getKeys [self] (keys state)) ;TODO TEST                                                                                                                      

  Object                                                                                                                                                          
   (toString [self]                                                                                                                                              
     (str ("Bag: " (:state self)))))   

When I try to require it in a repl I get:

java.lang.ClassFormatError: Duplicate interface name in class file compile__stub/techne/bag/MapBag (bag.clj:12)

What is going on? How do I get a keys function on my bag? Also am I going about this the correct way by assuming clojure's keys function eventually calls getKeys on the map that is its argument?


Solution

  • Defrecord automatically makes sure that any record it defines participates in the ipersistentmap interface. So you can call keys on it without doing anything.

    So you can define a record, and instantiate and call keys like this:

    user> (defrecord rec [k1 k2])
    user.rec
    user> (def a-rec (rec. 1 2))
    #'user/a-rec
    user> (keys a-rec)
    (:k1 :k2)
    

    Your error message indicates that one of your declarations is duplicating an interface that defrecord gives you for free. I think it might actually be both.

    Is there some reason why you cant just use a plain vanilla map for your purposes? With clojure, you often want to use plain vanilla data structures when you can.

    Edit: if for whatever reason you don't want the ipersistentmap included, look into deftype.