从let绑定中递归 - Scheme

时间:2015-08-06 13:27:45

标签: recursion scheme let

我知道Scheme是尾递归的,但如果一个函数在let绑定中调用自己,那么绑定值是否会保留在内存中,即使它们不需要?我有类似的代码:

(define (some-function)
  (let ((c (read-char)))
    (some-function)))

实际功能有点复杂,但由于流程等因素相同,因此无关紧要。因为从let块内部调用函数,是否会保留'c'的值?如果这个功能持续一段时间,我不想浪费大量的额外内存。

3 个答案:

答案 0 :(得分:2)

如果一个函数是尾递归的,那么如果递归函数调用是作为函数的一部分计算的最后一个东西,在你的代码就是这种情况,递归不会在内存中保留任何局部变量,所以栈空间消耗保持不变。

如果它不是递归递归,那么如果在递归之后仍然有一些事情要做,那么c必须保留在内存中,使得函数消耗O(n)堆栈空间其中n是递归的数量。一个例子就是:

(define (some-function)
  (let ((c (read-char)))
  (+ c (some-function))))

这里将在递归之后执行添加,因此在执行递归调用时必须将c保留在内存中。

答案 1 :(得分:2)

考虑你的例子:

(define (some-function)
  (let ((c (read-char)))
    ; <HERE>
    (some-function)))
(some-function)

假设我们运行此程序。该程序将一遍又一遍地循环调用some-function。在某些时候,垃圾收集器被激活。假设为了参数,进程(正在运行的程序)在标记为<here>的点上被中断。

垃圾收集器的工作是回收不再使用的内存。 “使用中”是什么意思?

正在使用一块内存,如果在程序继续时可能会使用它(或占用内存的值)。名称c绑定到一个字符,因此无法回收该字符。但是,由于对some-function的调用是尾递归的,因此一次只调用一次some-function。这意味着可以回收c的所有旧值。

对于循环函数尾部调用的命名循环,仅使用最新的值。 (这些值会保留在内存中,直到垃圾收集器被激活为止。)

一个小实验,试试:

(let loop ((v 0))
  (loop (make-vector 100000 42))

这个程序循环和循环。对于每一轮,分配长度为100000的向量。如果内存不足,您就知道v的值不是垃圾回收。如果程序不间断运行,那么垃圾收集器就可以回收内存。

答案 2 :(得分:1)

在尾部位置,绑定变量不需要存在。在呼叫时,只有创建过程时可用的变量才可用。

(define (some-function)
  ;; only global variables exist at this point
  (let ((c (read-char)))
    ;; c exists for a brief time 
    (some-function)))

在过程中评估变量some-function之后,在应用评估的c之前删除some-function。一次只存在一个c

想象一下这个稍微改动的版本:

(define (some-function previous)
  (let ((c (read-char)))
    (some-function c)))

除了some-function之前评估cc以及previous被删除且申请已开始,我们才会对此进行评估。

要求以这种方式处理过程和变量。它被称为tail call optimization

创建过程时,过程中使用的变量需要在过程存在期间存在:

(define (some-function2)
  (let ((c (read-char)))
    (lambda () c)))

(define test (some-function2))
(define test2 (some-function2))

现在。当some-function2调用完成时,变量将被移动到与过程相关的内容,并且仍然不是环境的一部分。这些过程称为闭包,每个过程中的c都是闭包变量,即使在绑定过程完成后也会存在。如果你习惯了其他语言,你可以想到这些变量存在于堆上。这些&#34;自由变量&#34;是什么让它在70年代创建时非常特别,而且每种编程语言都或多或少地基于这个原则。