我一直在尝试连续传递样式,因为我可能需要尽快处理一些非尾递归函数。无论如何,要知道的好技巧!我在Lua和Clojure中都编写了一个测试函数;在我的小型Android掌上电脑上运行Lua。
Lua版本似乎运行良好,Lua的堆栈已经有大约300000的深度,但是使用CPS,在系统崩溃之前,我很容易就能完成700万次迭代,这可能是由于内存不足,而不是CPS / Lua组合的任何限制。
Clojure尝试的效果不佳。在进行1000次以上迭代时,它抱怨堆栈过大,仅使用普通迭代(它的堆栈约为1600,iirc)就可以做得更好。
任何想法可能是什么问题? JVM固有的某些功能,还是一些愚蠢的noob错误? (哦,顺便说一句,选择了测试函数sigma(log)是因为它增长缓慢,而Lua在Android上不支持bignums)
欢迎所有想法,提示和建议。
Clojure代码:
user=> (defn cps2 [op]
#_=> (fn [a b k] (k (op a b))))
#'user/cps2
user=> (defn cps-sigma [n k]
#_=> ((cps2 =) n 1 (fn [b]
#_=> (if b ; growing continuation
#_=> (k 0) ; in the recursive call
#_=> ((cps2 -) n 1 (fn [nm1]
#_=> (cps-sigma nm1 (fn [f]
#_=> ((cps2 +) (Math/log n) f k)))))))))
#'user/cps-sigma
user=> (cps-sigma 1000 identity)
5912.128178488171
user=> (cps-sigma 1500 identity)
StackOverflowError clojure.lang.Numbers.equal (Numbers.java:216)
user=>
==================
PS。经过一番试验之后,我尝试了在下面的第三条注释中提到的代码
(defn mk-cps [accept? end-value kend kont]
(fn [n]
((fn [n k]
(let [cont (fn [v] (k (kont v n)))]
(if (accept? n)
(k end-value)
(recur (dec n) cont))))
n kend)))
(def sigmaln-cps (mk-cps zero? 0 identity #(+ %1 (Math/log %2))))
user=> (sigmaln-cps 11819) ;; #11819 iterations first try
StackOverflowError clojure.lang.RT.doubleCast (RT.java:1312)
按顺序,这显然更好,但是我仍然认为它太低了。从技术上讲,它应该仅受内存限制,是吗?
我的意思是,在玩具Android平板电脑上,玩具Lua系统的运行量超过700万...
答案 0 :(得分:5)
Clojure具有trampoline
函数,该函数可以消除很多与此问题有关的令人困惑的管道:
(defn sigma [n]
(letfn [(sig [curr n]
(if (<= n 1)
curr
#(sig (+ curr (Math/log n)) (dec n))))]
(trampoline sig 0 n)))
(sigma 1000)
=> 5912.128178488164
(sigma 1500)
=> 9474.406184917756
(sigma 1e7) ;; might take a few seconds
=> 1.511809654875759E8
您传递给trampoline
的函数可以返回一个新函数(在这种情况下,蹦床继续“弹跳”),也可以是一个非函数值,该值将是“最终”值。此示例不涉及相互递归的函数,但是它们也可以与trampoline
一起使用。