【问题标题】:Continuation-passing-style does not seem to make a difference in Clojure继续传递风格在 Clojure 中似乎没有什么不同
【发布时间】:2018-11-29 20:04:27
【问题描述】:

我一直在尝试继续传递风格,因为我可能需要很快处理一些非尾递归函数。无论如何,要知道的好技术!我用 Lua 和 Clojure 编写了一个测试函数;在我的小型 Android 手持设备上运行 Lua。

Lua 版本似乎运行良好,Lua 的堆栈已经有大约 300000 的深度,但是使用 CPS,我很容易在系统崩溃之前进行超过 7000000 次迭代,可能是因为内存不足,而不是CPS/Lua 组合的任何限制。

Clojure 的尝试不太顺利。仅在 1000 次以上的迭代中抱怨堆栈溢出,它可以通过普通迭代做得更好,它的堆栈约为 1600,iirc。

任何想法可能是什么问题?也许是 JVM 固有的东西,或者只是一些愚蠢的菜鸟错误? (哦,顺便说一句,选择测试函数 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)

按照订单,这显然更好,但我仍然认为它太低了。从技术上讲,它应该只受内存限制,是吗?

我的意思是玩具 Lua 系统,在玩具 Android 平板电脑上做了超过 7000000...

【问题讨论】:

  • Clojure 没有优化尾调用。我认为您将需要recurlooprecur。更多信息:clojuredocs.org/clojure.core/recur
  • 好点。有一会儿。我认为,由于我希望将其设置为在“堆”上工作(无论这在 JVM 中意味着什么),因此适用于 Clojure 和人类的正常规则不会生效。我会用'recur'重写它。像 CPS 和 y-combinator(我之前看过)这样的东西会让你觉得很奇怪。我敢肯定,最终一切都是好的。不知何故,我从未想过会使用 TCO 机制来允许在不使用堆栈的情况下实现非递归调用的机制。谢谢提醒!
  • 同时,如果有人有任何用规范 Clojure 编写的上述任何版本,或者任何提示、提示等,那就太好了。有趣的是,我使用的算法在 Lua 中运行良好。 Lua is 尾递归,但据我所知,我认为算法不是。如果我错了,请纠正我。
  • 我在这里找到了一组非常好的教程幻灯片:slideshare.net/borgesleonardo/… 基本上它涵盖了我的大部分问题。推荐给任何研究这个的人。

标签: recursion clojure continuations continuation-passing


【解决方案1】:

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 也是可行的。

【讨论】:

  • user=> (sigma 100000000) 1.7420680845246599E9 在我古老的摇摇晃晃的桌面上用了 20 多秒。这更像是它,我知道这是可行的。我现在得去取证,看看其他人有什么问题。再次感谢泰勒!
猜你喜欢
  • 2012-04-27
  • 1970-01-01
  • 1970-01-01
  • 2019-11-02
  • 2012-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多