【问题标题】:Clojure: defRecord, defProtocol: doing expensive calculation only onceClojure: defRecord, defProtocol: 只做一次昂贵的计算
【发布时间】:2012-05-27 23:07:19
【问题描述】:

上下文

考虑下面这段代码

(defprotocol ICat "Foo"
  (meow [cat]))

(defrecord Cat [a b] "Cat"
  ICat
  (meow [cat] (some-expensive-operation a b)))

问题

有什么方法可以让我在某个地方放一个 let 吗?

我希望 (some-expensive-operation a b) 在我执行时只评估一次

(->Cat a b)

这样在(喵喵)的时候,它只是返回预先缓存的值,而不是动态地重新计算它。比如:

[1] (let [x (->Cat a b)]
[2]   (meow x)
[3]   (meow x)
[4]   (meow x))

我希望 (some-expensive-operation) 在 [1] 处只计算一次,然后对于 [2]、[3]、[4] 它只返回旧值。

【问题讨论】:

    标签: clojure


    【解决方案1】:

    我建议在构造函数中包装一次调用昂贵操作的逻辑,并将结果作为常规值存储在记录中:

    (defprotocol ICat "Foo"
      (meow [cat]))
    
    (defrecord Cat [a b] "Cat"
      ICat
      (meow [cat] (:meow cat)))
    
    (defn make-cat [a b]
      (assoc (->Cat a b) :meow (some-expensive-operation a b)))
    

    当你的代码变得更复杂时,我发现你经常想在任何情况下定义自己的构造函数。

    请注意,您可能还需要考虑将昂贵的操作包装在惰性序列或延迟中,以便仅在需要时才计算它。

    【讨论】:

    • 一个delay,不是惰性序列。
    • 明确一点,这里的“构造器”不是指 Java 构造器,而是包装器,对吧?
    • 构造函数在这里的意思是“创建并返回记录类型实例的函数”。它不是 Java 构造函数,但它确实实现了几乎相同的目的。
    【解决方案2】:

    如果您的函数是引用透明的,那么您可以将您的函数包装在memoize 中。至少,您可以:

    (def memo-some-expensive-function (memoize some-expensive-function))
    

    然后在你的记录中使用memo-some-expensive-function

    【讨论】:

    • 然而,当不再需要 defrecord 时,这不会被 gc-ed。
    • 我更喜欢“卡”在 defrecord 上的东西,这样当 defrecord 被 gc-ed 时,它也会消失。
    • 您也许可以在记录中做到这一点,但我不确定该功能的记忆范围。值得研究...
    【解决方案3】:

    我开始相信解决这个问题的最简单方法可能是

    • 围绕 defrecord 创建一个包装器

    • 此包装器允许我指定要重新计算的其他字段

    • 并将这些额外字段附加到“真实”defrecord

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-27
      相关资源
      最近更新 更多