SICP 习题 1.46 要求我们写一个过程iterative-improve,它以两个过程为参数,其中一个参数用来检测猜测是否足够好,另一个参数用来改进猜测。过程iterative-improve应该返回另一个过程,所返回的过程接收一个参数作为初始猜测,然后不断改进猜测直到结果足够好。题目还要求我们使用iterative-improve重写1.1.7的sqrt过程和1.3.3节的fixed-point过程。
因为涉及到高阶函数,所以整个题目理解起来有一点点费劲。不过这道题作为第一章的收官题确实非常合适,它涉及到“过程作为参数”,也涉及到“过程作为返回值”,这两点差不多已经是高阶函数的全部了。当然,还有最重要的一点,就是高阶函数的灵魂——抽象,是对一般过程的抽象,也包括对抽象过程的抽象。这里涉及到的就是将sqrt和fixed-point这两个看上去相差挺大的两个过程抽象成一个通用的过程。
为了完成这道题,我们需要先回去看看1.1.7节定义的sqrt过程,过程代码如下:
(define (sqrt-iter guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x)))
如果我们将sqrt过程中通用的东西抽象出来,关键的东西有三样:
1. 当前猜测,
2. 判断结果是否足够好,
3. 改进猜测。
用语言来表达的话,对以上三样关键东西的组织形式如下:
看当前猜测是否足够好,好的话就返回当前猜测,如果不够好就通过某种手段改进猜测,再次对新的猜测进行判断。
如果用伪代码来表示大概情形如下:
(define (my-proc 判断猜测的方法 改进猜测的方法 当前猜测) (if (当前猜测足够好) 当前猜测 (my-proc 判断猜测的方法 改进猜测的方法 (改进猜测的方法 当前猜测))))
对应的代码如下:
(define (improve-guess-inner good-enough? improve current-guess) (if (good-enough? current-guess) current-guess (improve-guess-inner good-enough? improve (improve current-guess)))
书中还提到,要求我们做的方法iterative-improve应该返还一个过程,这个过程接受初始猜测作为唯一参数,然后启动不断改进猜测的计算。
所以将以上代码打包一下可以得出我们的iterative-improve过程:
(define (iterative-improve good-enough? improve) (define (improve-guess-inner good-enough? improve current-guess) (if (good-enough? current-guess) current-guess (improve-guess-inner good-enough? improve (improve current-guess)))) (lambda (guess) (improve-guess-inner good-enough? improve guess)))
注意它返回的是一个lamda函数。
好,以上是题目的前半部分,后半部分要求我们用iterative-improve重写sqrt过程和fixed-point过程。
如果是直接调用improve-guess-inner过程的话,新的sqrt过程就可以写成这样:
(improve-guess-inner sqrt-good-enough? sqrt-improve 1.0)
不过我们的improve-guess-inner是内部方法,不能直接使用,我们需要使用iterative-improve来调用,调用方法如下:
((iterative-improve sqrt-good-enough? sqrt-improve) 1.0)
其中的good-enough?过程为:
(define (sqrt-good-enough? guess) (< (abs (- x (* guess guess))) small-number)
其中的sqrt-improve过程如下:
(define (sqrt-improve cur-guess) (/ (+ (/ x cur-guess) cur-guess) 2))
所以,新的sqrt过程整体实现如下:
(define (new-sqrt x) (define (sqrt-good-enough? guess) (< (abs (- x (* guess guess))) small-number)) (define (sqrt-improve cur-guess) (/ (+ (/ x cur-guess) cur-guess) 2)) ((iterative-improve sqrt-good-enough? sqrt-improve) 1.0))
再来看看1.3.3节中得fixed-point过程的定义,代码如下:
(define (fixed-point f first-guess) (define (close-enough? v1 v2) (< (abs (- v1 v2 )) tolerance)) (define (try guess) (newline) (display (+ 0.0 guess)) (let (( next (f guess))) (if (close-enough? guess next) (+ 0.0 next) (try next)))) (try first-guess))
这段代码看上去和sqrt程序差别还挺大,不过,如果你仔细观察过程fixed-point,可以发现它的关键思想也是和sqrt一样的,就是看当前猜测是否足够好,好的话就返回当前猜测,如果不够好就通过某种手段改进猜测,再次对新的猜测进行判断。。。
其中判断猜测的方法就是那个close-enough。
其中有一个问题,前面的good-enough?方法只接受一个参数,而close-enough接受两个参数,判断两个参数是否足够接近。
要解决这个问题,我们需要考察fixed-point的工作原理,其中close-enough需要判断的是guess 和 (f guess)是否足够接近,所以我们可以这样从新定义close-enough
(define (fp-good-enough? cur-guess) (< (abs (- cur-guess (f cur-guess))) small-number))
用来比较cur-guess和(f cur-guess)是否足够接近,注意这个过程必须定义在过程f有效的地方。
对于fixed-piont 过程来讲,改进猜测的方法就是f本身,有关这一点如果不明白就需要回去看看1.3.3节有关fixed-point过程的讨论。
所以新的fixed-point过程可以写成这样:
(improve-guess-inner fp-good-enough? f guess)
因为improve-guess-inner不能直接调用,所以使用iterative-improve来调用的话代码如下:
((iterative-improve fp-good-enough? f) first-guess)
最终完成的new-fiexed-point过程如下:
(define (new-fixed-point f first-guess) (define (fp-good-enough? cur-guess) (< (abs (- cur-guess (f cur-guess))) small-number)) ((iterative-improve fp-good-enough? f) first-guess))
最后还有一个就是定义small-number等于多少:
(define small-number 0.000001)
以上就是SICP习题1.46的解题总结,其中有几个重点,一个是“过程作为参数”,一个是“过程作为返回值”,还有就是对一般过程的抽像,通过一般过程的抽象产生更为通用的过程。
最后还有一个隐藏的知识点,就是lambda函数中的变量作用范围,这个在写代码的时候需要特别留意。