这个问题的“显而易见”答案的一个问题是它并不能很好地工作。考虑一下:
(define (gen-list start end)
(if (> start end)
'()
(cons start
(gen-list (+ start 1) end))))
好吧,如果start 比end 少得多,堆栈上将会有大量的递归调用,因为这是一个适当的递归函数:对gen-list 的递归调用是一个真正的调用并且必须在调用cons(这是一个尾调用)之前返回。
解决这个问题的方法是将看起来像(cons x (<recursive-call> ...)) 的模式转换成看起来像(<tail-call> ... (cons x ...)) 的模式:你需要一个带有额外参数的函数,一个累加器。这意味着以前递归的调用现在是尾调用,因此一切都很好:这个过程现在是迭代的。
这样做的问题是列表倒退(您需要考虑为什么会这样,但经过一番思考后就很明显了)。因此,您需要反转结果。幸运的是,反转列表也是一个迭代过程,所以没关系。
但在这种情况下,好吧,你可以倒数!所以一个简单的方法看起来像这样,使用本地定义的辅助函数(这可以定义为顶级函数,但是为什么要麻烦呢?):
(define (gen-list low high)
(define (gla i result)
(if (< i low)
result
(gla (- i 1) (cons i result))))
(gla high '()))
您可以看到这是倒数:对gla 的初始调用以high 开头,然后倒数构造列表。所以,现在:
> (gen-list 1 3)
'(1 2 3)
如我们所愿。
这是 Scheme 中的一种常见模式,因此有一个特殊的构造:命名为 let。所以我们可以更习惯地把上面的内容改写为:
(define (gen-list low high)
(let gla ([i high] [result '()])
(if (< i low)
result
(gla (- i 1) (cons i result)))))
这与上一个答案完全相同:它只是将初始调用移动到顶部并将其与gla 的本地定义结合起来。这可能是做这种事情的惯用 Scheme 方式(尽管写的 Scheme 比我多的人可能会有所不同:我真的是一个 CL 人,并且不可避免地品味不佳)。
故事应该到此结束,但我忍不住添加以下内容。在过去 comp.lang.lisp 的糟糕日子里,人们过去常常问明显的家庭作业问题,并且由于没有因果报应系统,一种方法是给出解决问题的答案......同时非常不透明。
所以首先我们可以将gla 转换为一个函数,该函数通过一个继续调用而不是知道它必须调用自己:
(define (gen-list low high)
(let ([gla (λ (cont i result)
(if (< i low)
result
(cont cont (- i 1) (cons i result))))])
(gla gla high '())))
然后,我们当然可以将(let ([x y]) ...) 变成((λ (x) ...) y):
(define (gen-list low high)
((λ (gla)
(gla gla high '()))
(λ (cont i result)
(if (< i low)
result
(cont cont (- i 1) (cons i result))))))
这是一个很好的、纯粹的答案……任何学生都想不出来。
当然,另一种更具恶意的方法是显式使用 Y 组合子。