SICP1.2-递归、尾递归 VS. 迭代

本文探讨了纯函数式编程语言如Scheme如何使用递归来替代迭代结构,并强调了数据不变性和尾调用优化的重要性。Scheme通过尾递归实现迭代计算,避免栈溢出,而命令式语言如C、Java则倾向于使用迭代。文章通过举例展示了Scheme中递归求和的代码,并指出Java程序员在没有尾调用优化的情况下通常选择迭代语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【本文放入 0.2.3函数编程范式 返回OOD目录

Recursion vs. iteration

C/Java语言教学中,会讲循环/迭代语句;而Scheme会介绍递归。为什么纯函数式编程语言如Scheme不需要迭代结构如while、for呢?为什么纯函数式编程语言如Scheme要用递归而且能够用递归呢?

首先,数据的不变性是纯函数式编程语言的宗教信仰,它们不喜欢变量的值不断地变更,而for(int i=0;i < n;i++)中变量i的值总在变,对于Scheme而言,这是违反其基本价值观的异端。

其次,递归,更一般地,函数的调用,都有创建新的栈帧/stack frame的开销。如JVM为一个Java程序预备的内存有限,Java程序的如果有大量的递归调用将会出现栈溢出错误。如果函数对另外一个函数的调用是尾调用,解释器可以通过尾调用优化/tail-call optimization避免帧的创建。Scheme实现了尾调用优化,所以Scheme的递归函数如果是尾递归,事实上是迭代执行的(SICP1.2.1中比较含混地说明了这一点,尾递归达到Java使用for语句的效果,SICP中称其为“迭代计算过程”);当然,如果Scheme使用普通的递归(SICP中称其为“线性递归过程”),也会和Java递归一样,导致栈溢出错误。

Scheme中各种递归求和的代码如例程0-6所示。

;;;递归.rkt
;;;线性递归过程
(define (sum n)
  (if (= n 1)
      1
      (+ n (sum (- n 1)))))
;;;(sum 5000000) ;;;栈溢出

可以改为:

;;;尾递归
(define (sum-iter total n)
  (if (= n 1)
      total 
      (sum-iter (+ total  n) (- n 1))))

(define (sum n) (sum-iter 1 n))
(sum 5000000)

或:

;;;使用状态变量
(define (sum-iter total i n)
  (if (> i n)
      total
      (sum-iter (+ total i)
                (+ i 1)
                n)))

(define (sum n) (sum-iter 0 1 n))
(sum 5000000)

命令式语言最喜欢赋值语句,所以程序员使用循环/迭代语句非常自然和爽快,以至于C、Java编译器迟迟不愿意添加尾调用优化——没有动力。Scheme会迫使程序员将递归函数编写成尾递归,特别是在计算斐波那契数列这种树形递归;Java的尾递归也能够避免子问题被多次计算,但是在没有尾调用优化情况下,Java程序员会直接采用迭代语句而非尾递归。

    public static long sum_tail(final int total,final int n) {
        if (n == 0) return total;
        else return sum_tail( total + n , n - 1);
    }

而是采用迭代结构或流:

    public static long sum_Stream(long n){
        return java.util.stream.LongStream.rangeClosed(0, n)
        .reduce(0, (x,y)-> x + y);
    }

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值