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

时间:2016-08-10 18:16:33

标签: concurrency clojure synchronization stack-overflow memoization

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

我的具体情况如下:

; 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 个答案:

答案 0 :(得分:2)

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

  1. 一致的共享memoize缓存
  2. 非尾递归迭代而不吹栈
  3. 对于问题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函数调用结合起来,而不必担心"线程安全"