在Scheme
或Lisp
等函数式语言中,存在for
和for-all
循环。但是for
循环需要变异,因为它不是每次迭代的新堆栈帧。由于这些语言中没有明确的变异,这些函数式语言如何实现各自的迭代循环?
答案 0 :(得分:4)
使用引擎下的递归实现Scheme循环;诸如do
之类的构造只是被转换为递归过程的宏。例如,这个循环采用典型的过程语言:
void print(int n) {
for (int i = 0; i < n; i++) {
display(i);
}
}
......相当于Scheme中的以下程序;在这里你可以看到循环的每个部分(初始化,退出条件,增量,正文)都有一个相应的表达式:
(define (print n)
(define (loop i) ; helper procedure, a "named let" would be better
(when (< i n) ; exit condition, if this is false the recursion ends
(display i) ; body
(loop (+ i 1)))) ; increment
(loop 0)) ; initialization
您是否注意到在调用递归后没有什么可做的?编译器非常智能,可以优化它以使用单个堆栈帧,从而有效地使其与for
循环一样高效 - 有关更多详细信息,请阅读tail recursion。只是为了澄清一下,在Scheme中, 明确可用,请阅读set!
指令。
答案 1 :(得分:2)
这个问题实际上是两个问题,而且是一个混乱。
在Scheme中,迭代是通过递归和语言的语义一起实现的,该语言要求某些类型的递归不消耗内存,特别是尾递归。请注意,这并不意味着突变。因此,例如,这里是while
循环i Racket的定义。
(define-syntax-rule (while test form ...)
(let loop ([val test])
(if val
(begin
form ...
(loop test))
(void))))
正如您所看到的,对loop
的递归调用处于尾部位置,因此不会消耗内存。
传统的Lisp不要求尾部调用消除,因此需要迭代结构:这些通常由语言提供,但通常可以用较低级别的结构实现,例如GO TO。以下是Common Lisp中while
的定义:
(defmacro while (test &body forms)
(let ((lname (make-symbol "LOOP")))
`(tagbody
,lname
(if ,test
(progn
,@forms
(go ,lname))))))
Scheme和传统Lisps都提供了变异操作符:你可能认为它们都不是纯函数式语言。 Scheme更接近于one,但它仍然不是很接近。