lisp - Lisp中递归函数调用的堆栈溢出

标签 lisp common-lisp tail-recursion clisp land-of-lisp

我正在从 Conrad Barski 的“The Land of Lisp”一书中学习 Lisp。现在我遇到了我的第一个绊脚石,作者说:

Calling yourself in this way is not only allowed in Lisp, but is often strongly encouraged

在显示以下示例函数以计算列表中的项目之后:

(defun my-length (list)
  (if list
    (1+ (my-length (cdr list)))
    0))

当我使用包含一百万个项目的列表调用此函数 my-length 时,我收到堆栈溢出错误。因此,要么你永远不会期望 Lisp 中有这么长的列表(所以我的用例可能没用),要么有另一种方法来计算这么长的列表中的项目。你能不能对此有所启发? (顺便说一句,我在 Windows 上使用 GNU CLISP)。

最佳答案

CLISP 中的 TCO(尾调用优化)使用 Chris Taylor 的示例:

[1]> (defun helper (acc list)
       (if list
           (helper (1+ acc) (cdr list))
           acc))

(defun my-length (list)
  (helper 0 list))

HELPER

现在编译它:

[2]> (compile 'helper)
MY-LENGTH
[3]> (my-length (loop repeat 100000 collect t))

*** - Program stack overflow. RESET

现在,以上不起作用。让我们将调试级别设置为低。这允许编译器执行 TCO。

[4]> (proclaim '(optimize (debug 1)))
NIL

再次编译。

[5]> (compile 'helper)
HELPER ;
NIL ;
NIL
[6]> (my-length (loop repeat 100000 collect t))
100000
[7]> 

有效。

允许 Common Lisp 编译器执行 TCO 通常由调试级别控制。对于高调试级别,编译器生成的代码会为每个函数调用使用堆栈帧。这样每个调用都可以被跟踪并且可以在回溯中看到。对于较低的调试级别,编译器可能会在编译后的代码中用跳转替换尾部调用。然后,这些调用将不会在回溯中看到 - 这通常会使调试变得更加困难。

关于lisp - Lisp中递归函数调用的堆栈溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15269193/

相关文章:

lisp - lisp在线教程

f# - 即使有多个不同的递归调用,也可以针对尾递归优化函数吗?

lambda - Scheme 如何评估 man or boy 测试?

macros - Lisp源代码重写系统

graphics - 常见的 lisp 类型与 defgeneric 运行时分析

Common-Lisp:绑定(bind)形式参数,到底传递了什么?

operator-overloading - 覆盖/重载 + 运算符以对常见的 lisp 向量进行操作

f# - (如何)我可以让这个单子(monad)绑定(bind)尾递归吗?

c++ - 具有多个递归函数调用的 C++ 中的尾递归

带有可选数字前缀的 emacs 交互函数