SICP学习笔记(1.3.1)
周银辉
1,高阶函数
从小学数数 1 2 3 4 5,所以当有一天老师告诉你,它们称之为“自然数”时,你的脸上便没有任何吃惊的表情,因为在你看来它们是那么的“自然”。同样的,如果你开始便是接触的是函数式编程(而不是大学C语言)的话,那么在这里提到高阶函数,你的表情也应该是非常淡然,而不是迷惑。
在命令式编程语言中,我们习惯于将数、变量、对象作为函数参数与返回值;同样地,在函数式语言中,我们同样习惯于将 函数 作为 另一函数的参数或返回值(也可以是自身的参数或返回值,如果是递归的话),我们将这种“以函数为参数或返回值的函数”称为“高阶函数”或“高阶过程”。由于在函数式语言中,你所遇到的一切均是函数,(比如数字 3,它是 λfx.f (f (f x)) 这样的函数,在1.3.2讲lambda和丘奇数的时候会说道这个东西),所以,将函数作为参数或返回值则是最正常不过的事情了,它是那么的“自然”。
更多的,关于高阶函数的定义可以参考这里:http://en.wikipedia.org/wiki/Higher-order_function
2,Currying
这个应该放到1.3.2来说,但很奇怪的是,本节练习题1.33的最后一个小问却需要这个知识点,所以在这里简单提一下:Currying就是将拥有多个参数的函数化简成只有一个参数的形式。
比如在我们的印象中,假设我们需要这样定义两个数的求和运算: (define (add a b) (+ a b)) , 这需要对add方法传入两个参数,比如 (add 2 3); 如果我们只允许add 带一个参数,那应该怎么办呢? 我们应该采用Currying这个技巧编写出下面的代码:
(define (add a)
(lambda (b) (+ a b)))
此后,调用add方法就只需要传入一个参数了,比如 (((add 2) 3)
如果你还有所疑惑的话,不妨看看 Lambda calculus 以及 Continuation 的相关知识点。或者看看我接下来会写的“SICP学习笔记1.3.2”
3,练习1.29
用辛普森规则求积分,比较简单,基本上是将辛普森规则翻译成Scheme代码就可以了:
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
(define (cube a) (* a a a))
(define (F f a b n)
(define h (/ (- b a) n))
(define (y k) (f (+ a (* k h))))
(define (term i)
(+ (y (- (* 2 i) 2))
(* 4 (y (- (* 2 i) 1)))
(y (* 2 i))))
(define (next i) (+ i 1))
(/ (* h (sum term 1 next (/ n 2))) 3))
我用这段代码计算了一下几个值:
(F cube 0 1 10.0)
(F cube 0 1 100.0)
(F cube 0 1 1000.0)
(F cube 0 1 1000000.0)
运算结果:
0.25000000000000006
0.2500000000000001
0.2500000000000001
0.2499999999999972
4,练习1.30
先理解过程中各个参数的意思:
sum,求和运算
term,一个函数,(term a)就相当于数学中的F(a)
a,求和运算中自变量的起始点
b,求和运算中自变量的结束点
next,步进函数,即描述自变量如何从当前值步进到下一个值
OK,要完成这个练习,我们不妨先写一个专门针对连续整数的求和过程,也就是说,(terms a)的值为a,相当于数学中的f(x)= x ; 并且 (next n)为 n+1, 它的求和过程的尾递归版本如下:
(define (sum a b)
(sum-iter a b 0))
(define (sum-iter a b c)
(if (> a b)
c
(sum-iter (+ a 1) b (+ a c))))
关于如何建立该过程,请参考 SICP学习笔记(1.2.1 ~ 1.2.2) 中的“尾递归”节
那么按照上面的方式“依葫芦画瓢”,便可建立如下通用的求和版本:
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
(define (sum-iter term a next b cumulater)
(if (> a b)
cumulater
(sum-iter term (next a) next b (+ (term a) cumulater))))
5,练习1.31
利用高阶函数求Pi(∏ )
首先,按照求和的方法“依葫芦画瓢”,可以写出一下“求积”版本:
(define (product term a next b)
(product-iter term a next b 1))
(define (product-iter term a next b cumulater)
(if (> a b)
cumulater
(product-iter term (next a) next b (* (term a) cumulater))))
第n项的分子:
(define (den n)
(cond ((= n 1) 2.0)
((even? n) (+ 2.0 n))
(else (den (- n 1)))))
第n项的分母:
(define (num n)
(cond ((even? n) (num (- n 1)))
(else (+ n 2.0))))
第n项,函数F(n):
(define (F n)
(/ (den n) (num n)))
步进函数:
(define (increase a) (+ a 1))
然后就可以对pi求值了(我这里计算了前1亿项):
(* 4.0 (product F 1 increase 100000000))
计算结果(大概花了一分钟):
3.1415926692944294
6,练习1.32
没什么好说的,直接给的答案了:
(define (accumulate combiner null-value term a next b)
(accumulate-iter combiner term a next b null-value))
(define (accumulate-iter combiner term a next b result)
(if(> a b)
result
(accumulate-iter combiner term (next a) next b (combiner (term a) result))))
(define (F a) a)
(define (increase a) (+ a 1))
;为验证正确性,下面两个为测试函数
(accumulate + 0 F 1 increase 5)
(accumulate * 1 F 1 increase 5)
7,练习1.33
“过滤器”在2.2.3中会提到,这里可以简单地想象成一个检测装置:对于给定值a,如果满足给定条件condition,则返回值本身,否则返回空值 null-value,所以
;定义过滤器,这里我将其值打印出来了,以便看到过滤过程
(define (filter condition null-value a)
(cond ((condition a) (begin (display a) (display "; ") a))
(else null-value)))
;定义filtered-accumulate,其为练习1.32的accumulate增强版
(define (filtered-accumulate combiner null-value term a next b filter-condition)
(accumulate-iter combiner term a next b null-value null-value filter-condition))
;定义filtered-accumulate的尾递归版本
(define (accumulate-iter combiner term a next b null-value result filter-condition)
(if(> a b)
result
(accumulate-iter combiner term (next a) next b null-value
(combiner
(filter filter-condition null-value (term a)) result)
filter-condition)))
;定义素数检测函数
(define (prime? n)
(= n (smallest-divisor n)))
(define (divides? a b)
(= (remainder b a) 0))
(define (square a)
(* a a))
(define (find-divisor n test-divisor)
(cond ((> (square test-divisor) n) n)
((divides? test-divisor n) test-divisor)
(else (find-divisor n (+ test-divisor 1)))))
(define (smallest-divisor n)
(find-divisor n 2))
;定义小于N并且与N互素的检测函数
(define (gcd-n? n)
(lambda(a)
(and (< a n) (= (gcd a n) 1))))
;定义恒等函数 f(x)=x
(define (F a) a)
;定义增长率
(define (increase a) (+ a 1))
;计算1~100间的素数之和
(filtered-accumulate + 0 F 1 increase 100 prime?)
;计算小于100并且与100互素的正整数之乘积
(filtered-accumulate * 1 F 1 increase 100 (gcd-n? 100))
注意到上面定义的“小于N并且与N互素的检测函数”,其用到了我们上面所说的“currying”技巧,其仅仅带有一个参数n,如果我们按照常规的方式书写:
(define (gcd-n? n a)
(and (< a n) (= (gcd a n) 1)))
其带了两个参数,我们很快就会发现你无法带入到我们的“过滤器”函数中:
(define (filter condition null-value a)
(cond ((condition a) (begin (display a) (display "; ") a))
(else null-value)))
因为(condition a)是只带一个参数的。
注:这是一篇读书笔记,所以其中的内容仅属个人理解而不代表SICP的观点,并随着理解的深入其中的内容可能会被修改