如何使用核心clojure函数来处理我的defrecords

时间:2010-09-19 02:54:51

标签: clojure protocols frequency census

我有一个叫做袋子的defrecord。它的行为类似于要计数的项目列表。这有时被称为频率或人口普查。我希望能够做到以下

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

我尝试了以下内容:

(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)))))   

当我尝试在repl中要求它时,我得到:

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

发生了什么事?如何在包上获得按键功能?我也是通过假设clojure的键函数最终在地图上调用getKeys作为其参数来正确的方式吗?

2 个答案:

答案 0 :(得分:4)

Defrecord会自动确保它定义的任何记录都参与ipersistentmap界面。因此,您可以在不使用任何内容的情况下调用密钥。

所以你可以定义一个记录,并实例化和调用这样的键:

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

您的错误消息表明您的一个声明正在复制defrecord为您提供免费的界面。我认为它实际上可能都是。

为什么你不能只为你的目的使用普通的香草地图?使用clojure时,您通常希望尽可能使用普通的vanilla数据结构。

编辑:如果由于某种原因您不希望包含ipersistentmap,请查看deftype。

答案 1 :(得分:3)

罗布的回答当然是正确的;我发布这篇文章是为了回应OP对它的评论 - 也许它可能有助于用deftype实现所需的功能。

我曾经为Clojure编写了一个“默认地图”的实现,它的作用就像一个普通的地图,除了当被问及里面没有一个键时它会返回一个固定的默认值。代码位于this Gist

我不确定它是否会直接适合您的用例,尽管您可以使用它来执行

之类的操作
user> (:earth (assoc (DefaultMap. 0 {}) :earth 8000000000))
8000000000
user> (:mars (assoc (DefaultMap. 0 {}) :earth 8000000000))
0

更重要的是,它应该让您了解使用deftype编写此类内容所涉及的内容。

然后再次,它基于clojure.core/emit-defrecord,所以你可能会看一下Clojure的源代码......它正在做很多你不需要的事情(因为它是准备宏的功能)扩展 - 里面有很多语法引用等等你必须脱掉它才能直接使用代码),但它肯定是最高质量的信息来源。 Here是Clojure 1.2.0版本源代码中的直接链接。

更新

我意识到的另一件事可能很重要。如果您依赖特殊的类似地图类型来实现此类事物,则客户端可能会merge将其放入常规映射中并丢失该过程中的“默认”功能(实际上还有任何其他特殊功能)。只要您的类型维护的“地图般的”错觉足以将其用作常规地图,传递给Clojure的标准函数等,我认为可能没有办法解决这个问题。

因此,在某种程度上,客户可能必须知道存在一些“魔法”;如果他们得到(:mars {...}):mars中没有{...})等查询的正确答案,他们就必须记住不要merge将其转换为常规地图({ {1}} - 反过来会很好的工作。)