帮我把这个noob代码更加惯用

时间:2011-03-30 23:01:44

标签: clojure

所以我使用congomongo(接近结尾的fetch函数)从mongodb集合中提取一些文档。我想将选项传递给fetch调用,因此我可以执行类似(posts :limit 1)的操作并将{:limit 1}传递给fetch。我正在使用@posts进行手动“memoization”,因为我希望能够重置缓存,我的理解不能用clojure.core/memoize完成。

现在,我在这里看到的问题是(fetch :posts options)调用非常简单,如果dosync必须重试事务,我真的不想锤击我的数据存储区。我虽然是一个完全的clojure / fp noob,我不知道如何解决这个问题。此外,由于我是一个菜鸟,如果我在这里做任何让你感到畏缩的事情,我很想知道如何正确地写这个。

(def posts (ref nil))
(defn reset-posts [] (dosync alter posts nil))

(defn fetch-posts [& options]
  (let [options (apply array-map options)]
    (or @posts
        (dosync alter posts (fetch :posts options)))))

3 个答案:

答案 0 :(得分:6)

我不相信你的交易阻止((dosync alter ...)符合你的想法!

user=> (def posts (ref nil))
#'user/posts
user=> (dosync (ref-set posts [1 2 3 4 5]))
[1 2 3 4 5]
user=> @posts
[1 2 3 4 5]
user=> (dosync alter posts nil)
nil
user=> @posts
[1 2 3 4 5]

reset-posts中,您可能需要(dosync (ref-set posts nil)),而在fetch-posts中,语法修复将为(dosync (ref-set posts (fetch :posts options)))

然而,fetch-posts中存在竞争条件,即行为检查。可能不是那么大的交易;不确定谁使用fetch-posts,但在事务中移动or @posts位将避免2个并发事务最终提交更改的情况。

关于fetch-posts的重试,是的,虽然你的缓存解决方案避免了大部分问题,但可能会发生这种情况。不过,我不确定它是否有办法绕过它。通常在事务中使用I / O内容将其存储到代理,但事务的成功取决于fetch的返回值,因此我不清楚它是如何工作的。

答案 1 :(得分:1)

所以你引入了引用,因为你希望能够在时间过去时不会炸掉内存,因为只是在fetch-posts周围使用memoize可能会导致这个,迟早,对吗?

也许你可以尝试另一种方法:让fetch-posts变得“纯粹”,无记忆。在这种情况下,有人可以无视地调用fetch-posts,而不必担心OutOfMemoryExceptions。 实际上,对于某些用例来说,在调用代码的本地“缓存值”可能就足够了。

但故事并没有在这里结束,或者我不会花时间回答:-):你可以通过使用clojure.core / binding重新绑定fetch-posts来轻松地让你的“及时本地化”备忘录:来自然后,调用堆栈中同一线程中的所有代码都将受益于绑定的memoized fetch-posts。 如果您正在使用clojure 1.3 alpha,则需要通过:dynamic metadata将fetch-posts var声明为可重新绑定。

;; most simple definition
(defn ^:dynamic fetch-posts [& options]
  (let [options (apply array-map options)]
    (fetch :posts options)))

;; a la carte caching by the calling code (lexically scoped)
(let [posts (apply fetch-posts options)] ...)

;; a la carte caching by the calling code (dynamically scoped)
(binding [fetch-posts (memoize fetch-posts)] ...)

我的最后一个猜测是,您希望在原始版本的帖子中“记忆”,通过一个密钥索引帖子,这将是seq的选项,对吧?有些人可能你的代码不对吗? (或者你假设fetch-posts总是会一遍又一遍地用相同的args调用?)

另一个想法。使用代理序列化对帖子的写访问权限,然后确保只有在nil时才会调用fetch:

    (def posts (agent nil))

    (defn reset-posts [] (send posts (constantly nil)))

    (defn fetch-posts [& options]
      (let [options (apply array-map options)]
        (send-off posts #(or % (fetch :posts options)))
        (await-for (Long/MAX_VALUE) posts)
        @posts))

答案 2 :(得分:1)

dosync之外移动大量计算可能有用的另一种方法是使用delay

(defn fetch-posts
  [& options]
  @(dosync (or @posts (ref-set posts (delay (apply fetch :posts options))))))

另请注意,您的原始代码是线程安全的,因为您访问dosync之外的ref并在dosync之后根据此值对其进行修改。但是derefdosync之间的值可能已经发生了变化。例如。另一个并行调用fetch-posts的线程。

此外,代理方法也值得怀疑,因为您无法可靠地阅读代理。您获得的值是一致的,但访问不同步。考虑一下Laurent的例子:在await-forderef之间,另一个线程可能已经调用了reset-posts,而您获得了nil而不是帖子数据。在这个例子中,这可能是a)远程提取和b)可能是一个必须考虑的情况,但可能有其他用例,这会在更关键的代码中引入微妙的竞争条件。

tl;博士:小心你的所作所为! Clojure不是神奇的线程安全。彻底了解您的解决方案,并了解其影响。