在 Clojure 中我应该在哪里使用 defrecord?

我在我的 Clojure 程序中使用了许多映射和结构。将这些数据转换为解除记录有什么好处(除了性能之外) ?

15336 次浏览

另一个主要的好处是记录有一个类型(它的类) ,您可以分派出去。

下面的例子使用了这一特性,但并不能代表所有可能的用途:

(defprotocol communicate
(verbalize [this]))


(defrecord Cat [hunger-level]
communicate
(verbalize [this]
(apply str (interpose " " (repeat hunger-level "meow")))))


(defrecord Dog [mood]
communicate
(verbalize [this]
(case mood
:happy "woof"
"arf")))


(verbalize (->Cat 3))
; => "meow meow meow"


(verbalize (->Dog :happy))
; => "woof"

我认为结构被有效地弃用了,所以我根本不使用它们。

当我在许多映射实例中使用一组固定的已知键时,我通常会创建一条记录。最大的好处是:

  • 表演
  • 生成的类有一个类型,我可以在多方法或其他情况下打开它
  • 通过使用 defrecord 周围的其他宏机制,我可以获得字段验证、默认值和其他我想要的东西
  • 记录可以实现任意的接口或协议(映射不能)
  • 记录在大多数情况下充当地图
  • Key 和 val 以稳定(每次创建)的顺序返回结果

记录的一些缺点:

  • 因为记录是 Java 类实例(而不是 Clojure 映射) ,所以没有结构共享,所以相同的记录结构可能比已更改的等效映射结构使用更多的内存。当您“更改”一条记录时,也会产生更多的对象创建/破坏,尽管 JVM 的设计是专门为了不费吹灰之力地吃掉这种短暂的垃圾而设计的。
  • 如果在开发过程中更改记录,那么可能需要更频繁地重新启动 REPL 以获取这些更改。这通常只是开发过程中的一个小问题。
  • 许多现有的库尚未更新以支持记录(postwalk、 zip、 match 等)。我们已经根据需要添加了这个支持。

Stuart Sierra 最近写了一篇关于“用 Clojure 1.2解决表达式问题”的有趣文章,其中也包含了关于 defrecord的部分:

Https://web.archive.org/web/20110821210021/http://www.ibm.com/developerworks/java/library/j-clojure-protocols/#datatypes

我认为整篇文章是理解协议和记录的一个很好的起点。

在大多数情况下使用映射,只有在需要多态性时才使用记录。单独使用映射仍然可以使用多方法; 但是,如果需要协议,则需要记录。考虑到这一点,等到需要协议的时候再求助于记录。在此之前,请避免使用它们,而选择更以数据为中心、更简单的代码。

除了前面提到的,除了在性能方面通常达到或超过标准,以及在显示与 map 相同的编程接口时,记录强制执行温和的结构: 在定义时强制执行键名和键数。这可能有助于避免出现愚蠢的错误,因为这些错误需要从多个值中得到相同的结构(或者只是人为地严格执行)。

无论最初的动机是什么,这个属性也使它有别于地图。