【问题标题】:Where should I use defrecord in clojure?我应该在 clojure 中哪里使用 defrecord?
【发布时间】:2011-06-02 06:56:15
【问题描述】:

我在 clojure 程序中使用了许多映射和结构。将这些转换为 defrecord 有什么好处(除了性能)?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    我认为结构已被有效弃用,因此我根本不使用它们。

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

    • 性能
    • 生成的类具有我可以在多方法或其他情况下打开的类型
    • 使用围绕 defrecord 的附加宏机制,我可以获得字段验证、默认值以及我想要的任何其他内容
    • 记录可以实现任意接口或协议(地图不能)
    • 记录在大多数情况下充当地图
    • keys 和 vals 以稳定的(每次创建)顺序返回结果

    记录的一些缺点:

    • 因为记录是 Java 类实例(不是 Clojure 映射),所以没有结构共享,因此相同的记录结构可能会比已更改的等效映射结构使用更多的内存。当您“更改”记录时,还会有更多的对象创建/销毁,尽管 JVM 专门设计用于不费吹灰之力地吃这种短暂的垃圾。
    • 如果您在开发过程中更改记录,您可能需要更频繁地重新启动 REPL 以获取这些更改。这通常只是在狭隘的开发阶段才会出现的问题。
    • 许多现有库尚未更新以支持记录(postwalk、zip、ma​​tchure 等)。我们已根据需要添加了此支持。

    【讨论】:

    • 您在第三个参数中描述的构造函数形式是 Common Lisp 所指的“BOA 构造函数”--按参数顺序,它用于定义的音乐会类型与defstruct。见lispworks.com/documentation/HyperSpec/Body/…
    • 嗨,Alex - 我想你会发现记录实际上确实在底层使用了结构共享 - 它们实现了完整的持久数据结构,因此它们保留了常规映射的所有优点。
    • Mikera - 一条记录生成一个带有最终字段的 Java 类。关联成一个必须生成一个新的 Java 对象。它可以重用字段实例,因为它们是不可变的,但它不如普通的 Clojure 映射高效。
    • 你指的 defrecord2 是什么?我在 core 和 contrib 中都找不到它。
    • 我认为地图也更容易序列化/反序列化使用,例如,edn。我记得在读取之前序列化的记录时遇到了一些问题,因为执行反序列化的代码找不到我定义的记录类型的编译代码,尽管我最终以某种方式将其整理出来。如果我只使用地图,我会完全避免这个问题。
    【解决方案2】:

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

    http://www.ibm.com/developerworks/java/library/j-clojure-protocols/index.html#datatypes

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

    【讨论】:

    • 这是一个仅链接的答案。它至少需要一个摘要 imo。
    【解决方案3】:

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

    使用此功能但不代表所有可能用途的示例如下:

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

    【讨论】:

    • 并非如此,因为您可以调度任何函数,而不仅仅是键入。您可以使用普通地图,例如,添加另一个带有名为“type”的键的条目。然后你可以使用一个调度函数来检查“type”条目的值。
    • @Goran,适用于多方法,使用协议,您只能按类型分派。无论如何,我的观点是使用 defrecords 隐式添加类型,而 defstruct 或 map 不会自动添加类型。
    • 这个答案真的很简洁,一个例子可能是一个很好的补充。
    • 添加示例
    【解决方案4】:

    在大多数情况下使用映射,仅在需要多态性时使用记录。仅使用地图,您仍然可以使用多种方法;但是,如果您需要协议,则需要记录。鉴于此,等到您需要协议后再诉诸记录。在此之前,请避免使用它们,转而使用更以数据为中心且更简单的代码。

    【讨论】:

      【解决方案5】:

      除了前面提到的之外,除了在性能方面通常达到或优于标准外,并且在将相同的编程接口暴露为映射时,记录强制执行温和的结构:键名和键数强制执行为定义的时间。这可能有助于避免愚蠢的错误,即许多值都需要相同的结构(或者只是人为的刚性)。

      无论最初的动机是什么,这个属性也将它与地图区分开来。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-01
        • 2017-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多