如何在Clojure中结合非尾递归+有效和同步的记忆+有界的堆栈消耗



如何在Clojure中将非尾递归与同步记忆和有界堆栈消耗(因此没有堆栈溢出的风险)结合起来?通过同步记忆,我的意思是备忘录/缓存必须并发地并有效地在线程之间共享。

我的具体情况如下:

; g() is non recursive
; i is an integer
; h is a hash with int keywords and vector of ints values
; w is a hash with int keywords and int values
(defn g [i h w]
  (filter
    #(-> (w %)
         (= i))
    (h i)))
; f is recursive, recurses non-trivially (non-tail, multiple times)
; TODO: be memoizable (ideally in a synchronized way, for parallelism)
; TODO: pose no risk stack overflow
(defn f [i h w]
  (if (nil? (h i))
    0
    (let [part_sum
      (map                     ; will change this map to pmap or pvmap
        #(f % h w)
        (g i h))]
      (-> (reduce + part_sum)
          (/ 2)
          (+ 1)))))
; trivial, shown for completeness
(defn ff [i h w]
  (-> (f i h w)
      (- 1)
      (* 2)
      (max 0)))

幸运的是,这些问题可以独立解决:

  1. 一致的共享内存缓存
  2. 不破坏堆栈的非尾部递归迭代

对于问题1,您需要首先决定应该在什么时候填充缓存。如果在开始计算函数时填充它。这意味着应该绝对保证每个函数只运行一次,即使在第一个函数运行时进行了第二个调用。或者,如果您希望允许同时发生对函数的两个调用,并且只将其中一个存储到缓存中。与此略有不同的是,您只需将最后返回的结果存储到缓存中。

如果你只调用

,最后一种方法就是默认的结果
(def memoized-function (memoize function-name))

对于几乎所有情况都是足够的。如果您需要其他选项,那么让您希望记忆的函数返回future而不是结果,并且仅返回deref您在使用它们之前从缓存中获得的值。

对于选项二,内置的trampoline函数允许您拥有常数堆栈非尾部递归函数。将函数更改为在基本情况下(当递归完成时)返回一个不是函数(只是一个正常结果)的值,并在需要进一步递归时返回一个函数。然后trampoline函数反复"反弹"到函数中,直到一个值从另一边掉出来。它看起来像这样:

user> (defn foo-helper [x]
        (let [result 
              (if (pos? x)
                #(foo-helper (dec x))
                x)]
          (println "foo" x)
          result))
#'user/foo-helper
user> (trampoline foo-helper 4)
foo 4
foo 3
foo 2
foo 1
foo 0
0

因此,您可以将Clojure的正常缓存与正常的trampline函数调用结合起来,而不必担心"线程安全"

最新更新