如何在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,您需要首先决定应该在什么时候填充缓存。如果在开始计算函数时填充它。这意味着应该绝对保证每个函数只运行一次,即使在第一个函数运行时进行了第二个调用。或者,如果您希望允许同时发生对函数的两个调用,并且只将其中一个存储到缓存中。与此略有不同的是,您只需将最后返回的结果存储到缓存中。
如果你只调用
,最后一种方法就是默认的结果(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函数调用结合起来,而不必担心"线程安全"