views:

84

answers:

2

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?

+3  A: 

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.

Rob Lachlan
interesting. Thank you. There is a reason for this to not be a vanilla map. If I have a census of people living on planets: I might want something like: (get planets earth) => 8billion, (get planets mars) => 0. A vanilla map will give me nil people on mars, unless I use (get planets mars 0). I don't want the client to have to know that.
Nick Orton
+2  A: 

Rob's answer is of course correct; I'm posting this one in response to the OP's comment on it -- perhaps it might be helpful in implementing the required functionality with deftype.

I have once written an implementation of a "default map" for Clojure, which acts just like a regular map except it returns a fixed default value when asked about a key not present inside it. The code is in this Gist.

I'm not sure if it will suit your use case directly, although you can use it to do things like

user> (:earth (assoc (DefaultMap. 0 {}) :earth 8000000000))
8000000000
user> (:mars (assoc (DefaultMap. 0 {}) :earth 8000000000))
0

More importantly, it should give you an idea of what's involved in writing this sort of thing with deftype.

Then again, it's based on clojure.core/emit-defrecord, so you might look at that part of Clojure's sources instead... It's doing a lot of things which you won't have to (because it's a function for preparing macro expansions -- there's lots of syntax-quoting and the like inside it which you have to strip away from it to use the code directly), but it is certainly the highest quality source of information possible. Here's a direct link to that point in the source for the 1.2.0 release of Clojure.

Update:

One more thing I realised might be important. If you rely on a special map-like type for implementing this sort of thing, the client might merge it into a regular map and lose the "defaulting" functionality (and indeed any other special functionality) in the process. As long as the "map-likeness" illusion maintained by your type is complete enough for it to be used as a regular map, passed to Clojure's standard function etc., I think there might not be a way around that.

So, at some level the client will probably have to know that there's some "magic" involved; if they get correct answers to queries like (:mars {...}) (with no :mars in the {...}), they'll have to remember not to merge this into a regular map (merge-ing the other way around would work fine).

Michał Marczyk