对SCHEME的一些理解(2)
平时抽空看看SICP,主要是为了理解一些UNIX设计问题,另外也能揣摩最早一批计算机黑客的技术发展历程,UNIX将一切都是文件,一切最好都用文本的思想全面贯彻到系统设计中,想要领悟其哲学,必定得掌握最接近机器模型的C语言和最强文本处理能力的LISP,再说,一代UNIX黑客几乎都是LISP黑客(主要是MIT人工智能实验室中的那群人),UNIX也是其最主要的两位作者将经验主义和理论主义完美结合的典范,个人觉得Ken Thompson设计的UTF-8编码就有浓重的经验主义意味,也透露出一些无厘头的搞怪气氛,可能是又一次对KISS原则的贯彻吧。《UNIX编程艺术》和《黑客和画家》这两本具有浓厚技术黑客文化意味的书籍都有指出,程序员不是科学家,更像创作者,用现有的技术发明的新的创意,所以很有可能对于程序员来说,经验甚至比科学理论基础更重要。
言归正传,我在做SICP习题时,会拿一些我觉得比较有启发意义的题目做深入点的分析,给自己留个笔记(如上篇对习题2.6的分析)。现在来看习题2.18(我看书比较慢),这道题不算很难,但我觉得它着重点出了前递归和后递归的差别(没专门学过算法,不知道怎么描述),要打印题目要求的样式,list必须是前数据后pair模式,就是(car list)返回数据,(cdr list)返回还是个list,我看了下SICP之前都是用的前递归模式,所以我第一次写了这样一个习惯性的答案:
(define clist
(cons 21
(cons 22
(cons 23
(cons 24
(cons 25 ()
))))))
(define (reverse x)
(if (null? (cdr x))
(car x)
(cons (reverse (cdr x))
(car x))))
打印结果为:
((((25 . 24) . 23) . 22) . 21)
数字是倒过来了,可惜用的数据结构不对,这个打印结果表示成scheme语法如下:
(cons (cons (cons (cons 25 24) 23) 22) 21)
和想要的模式正好相反,(car list)返回的是list,而(cdr list)返回的是数字。造成这个结果的主要原因是reverse是前递归式的,也就是说递归展开后cons的操作是从前向后顺序计算的,我们需要将list的所有数据car出来,然后从后向前进行cons操作,也就是说cons操作要先于递归展开,有一种方法可以做到这点,就是将cons包含reverse的模式,改为reverse包含cons,将cons结果作为参数传递给reverse,这样就能在递归展开前进行cons操作了,修改后的reverse如下:
(define (reverse-1 x part)
(if (null? (cdr x))
part
(reverse-1 (cdr x)
(cons (car x) part)
)))
写的好看点:
(define (reverse k)
(define null ())
(define (reverse-1 x part)
(if (null? (cdr x))
part
(reverse-1 (cdr x)
(cons (car x) part)
)))
(reverse-1 k null))
总结一下,如果要在递归完成的同时就完全所有操作,就将要的进行的操作和判断条件通过参数传入函数,如果要在递归收敛时进行相关操作,就将递归函数作为参数传递给操作。