如何让核心 clojure 函数与我的 defrecord 一起使用

发布于 2024-09-19 15:46:08 字数 1981 浏览 9 评论 0原文

我有一个叫做包的解压记录。它的行为就像一个要计数的项目列表。这有时称为频率或普查。我希望能够执行以下操作,

(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 的 keys 函数最终在作为其参数的地图上调用 getKeys 来以正确的方式进行处理?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

堇色安年 2024-09-26 15:46:08

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,您通常希望尽可能使用普通的数据结构。

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

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.

噩梦成真你也成魔 2024-09-26 15:46:08

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

我曾经为 Clojure 编写过一个“默认映射”的实现,它的作用就像常规映射一样,只不过当询问其中不存在的键时它会返回一个固定的默认值。代码位于此要点中。

我不确定它是否直接适合您的用例,尽管您可以使用它来做类似的事情,

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

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

话又说回来,它是基于 clojure.core/emit-defrecord 的,所以你可能会看看 Clojure 源代码的那一部分......它做了很多你不需要做的事情(因为它是一个准备宏扩展的函数——里面有很多语法引用之类的东西,你必须将其剥离才能直接使用代码),但它肯定是可能的最高质量的信息源。 这里是直接的链接到 Clojure 1.2.0 版本的源代码中的该点。

更新:

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

因此,在某种程度上,客户可能必须知道其中涉及一些“魔法”;如果他们得到诸如 (:mars {...}) 之类的查询的正确答案({...} 中没有 :mars ),他们必须记住不要将其合并到常规地图中(合并 - 反过来也可以正常工作)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文