SICP习题1.6深入分析

来源:互联网 发布:淘宝军品店 编辑:程序博客网 时间:2024/05/16 13:46

new-if在用在递归函数时,会导致栈溢出,这是很多资料里提到的,对于资质愚鲁的我,这个结果还是略显跳跃了点,于是苦思了几天,分析如下:

先给出定义代码:


(define (new-if predicate if-clause then-clause)     (cond (predicate if-clause)           (else then-clause)     ))(define (TailSum num)  (new-if (= 0 num) 0 (+ num (TailSum (- num 1))))  )

上面的递归函数TailSum其实就是求1到n的累加,为什么会堆栈溢出呢,假如调用形式是这样的:


(tailsum 1)


那么解释器会这样执行:

1、判断tailsum不是special form,求值所有入参,这里只有“1”

2、入参求值完毕,展开tailsum

3、展开后变成(new-if (= 0 1) 0 (+ 1 (TailSum (- 1 1))))

4、判断new-if不是special form,求值所有入参

4.1、求值入参(= 0 1)求值为#f

4.2、求值入参0求值为0

4.3、求值入参(+ 1 (TailSum (- 1 1))

4.3.1、判断+为special form,求值所有入参

4.3.2、求值入参1为1

4.3.3、求值入参(TailSum (- 1 1))

4.3.3.1、判断tailsum不是special form,求值所有入参

4.3.3.2、求值入参(- 1 1)为0

4.3.3.3、再次展开(tailsum 0)中的函数tailsum

4.3.3.3.1、展开后变成(new-if (= 0 0) 0 (+ 0 (TailSum (- 0 1))))

4.3.3.3.2、判断new-if不是special form,求值所有入参

4.3.3.3.3、求值入参(= 0 0)求值为#t

4.3.3.3.4、求值入参0求值为0

4.3.3.3.5、求值入参(+ 1 (TailSum (- 0 1))

4.3.3.3.5.1、判断+为special form,求值所有入参

4.3.3.3.5.2、求值入参0为0

4.3.3.3.5.3、求值入参(TailSum (- 0 1))

4.3.3.3.5.3.1、判断tailsum不是special form,求值所有入参

4.3.3.3.5.3.2、求值入参(- 0 1)为-1

4.3.3.3.5.3.3、再次展开(tailsum -1)中的函数tailsum

.....................................

可以看出,因为new-if不是special form,所以不像真正的if那样,在求出predicted后,只执行最后入参的其中一个,最终会只执行then-clause,然后返回

而new-if会被解释器当做用户定义的普通函数,根据“应用序”的求值规则,解释器会先把所有入参求值,然后才会调用用户定义的函数,然后其中一个入参恰好就是用户定义的函数,所以入参求值会永远进行下去,上面的调用即使是(tailsum 0),仍然会因为对入参的无限求值而最终耗尽堆栈


那么,如果解释器是正则序求值的,是不是就不会溢出了,答案是:是的

同样拿上面的例子来单步:

1、判断tailsum不是special form,先展开用户函数

2、展开后变成(new-if (= 0 1) 0 (+ 1 (TailSum (- 1 1))))

2.1、判断new-if不是special form,继续展开用户函数

2.2、展开后变成(cond ((= 0 1) 0)   (else (+ 1  (TailSum (- 1 1))))

2.3、判断cond是special form,但predicate为#f,跳到cond的else分支

2.3.1、判断(TailSum 0)不是special form,展开

2.3.2、展开为(new-if (= 0 0) 0 (+ 0 (TailSum (- 0 1))))

2.3.2.1、判断new-if不是special form,展开new-if

2.3.2.2、展开后变成(cond ((= 0 0) 0)   (else (+ 1  (TailSum (- 0 1))))

2.3.2.3、判断cond为special form,且predicate为#t,返回0

2.3.3、(new-if)返回0

2.3.4、(tailsum 0)返回0

2.4、cond返回else的+1为1

3、(tailsum 1)返回1


总结:

应用序是先求值入参,再展开函数

正则序是先展开函数,再求值入参


前者有求值顺序的问题,但节约栈空间;后者没有求值顺序的问题,但很费栈空间,且有冗余计算的情况