对SCHEME的一些理解(1)
最近抽空阅读SICP,并重温《黑客帝国》这个电影给了我在计算机程序设计上有了一些灵感的启发,在正式谈论SICP之前我想先对scheme及其在程序设计语言中的地位作个简单解释,LISP是一门非常古老的语言,据说仅FORTRAN比其老迈,在经历多年落寞之后LISP突然焕发光彩,新近的流行语言不断从中吸取养分,茁壮成长,比如C#加入LAMBDA表达式,JS的闭包,垃圾回收等,为什么LISP在分支方言众多,而且没有组织统一维护的不利状况下能如此潇洒得意。现在将自己的一些理解写下笔记,方便后面温习查询。
我喜欢scheme,因为他小且纯粹,我不指望能直接用LISP做一些现实开发,只希望靠它开启我的思维,很多人之所以觉得LISP入门极难,或者SICP这本书起点太高,只是因为我们基础太差,相信如果我们之前有离散数学、数理逻辑的基础,看起来必定轻车熟路,因为相比计算机编程语言,LISP更像数学,它比C/C++更为基础和原始,就如donald e.knuth所言,计算机科学只不过是数学的一层漂亮外衣,没有离散数学、组合数学、概率论、抽象代数、基础微积分等基础数学知识,而直接阅读《计算机程序设计艺术》,不但异常艰难,而且收效甚微,所以在看这套计算机科学经典之前,最好先看看作者的另一本书《具体数学》,这是题外话,现在我证明给你看为什么lisp比C更为原始,夸张一点说:更接近真理本质。
打个不太恰当的比喻吧,你觉得1米/秒和1千克哪个大?你觉得数字1和+运算符有什么关系?你可能会说,你不是白痴吧,两个完全不同的东西,你怎能拿来类比,我想说的是,不要和"matrix"的奴隶一样,要学"neo"尽可能看透这个世界,很多人都相信真理是简单的,我也相信。几百年前没有人会觉得我们和植物有什么相同,而现在我们知道了,其实我们很像,都是由DNA/RNA传递遗传信息,都由细胞构成;几十年前没有人觉得我们和水有什么相同,现在我们知道了,其实我们都是由有限的几种元素组成。爱因斯坦发明了质能方程统一能量和质量,质量和能量其实可能只是一种事物的不同表现,不要被一切美丽的外表所迷惑,借用《黑客帝国》中的台词,何为真实?你所看到的?你所嗅到的?这些只不过是你大脑的反应。而现在的一切知识模型很可能在未来都会慢慢推翻,直到找到真理。
这里有一个可观察和可测量的概念,就如牛顿的万有引力,牛顿不知道万有引力到底是什么,他只是找到了它的作用规律,在没有找到真理之前,所有知识都只是事物的近似抽象模型,很多我们习以为常的概念却不是那么显而易见,比如:数字‘0’到底是什么,如何严格定义?你别告诉我一个圈就是个零,我也不清楚,但我知道这个问题不是那么显而易见,比如如果一个“东西”它和任何其他东西的积是它本身,它就是零,那么只要一个东西满足这个约束条件那他就是“0”,正好说明SICP第二章关于序对的阐释,不管底下如何组织序对,我只需要一个可以生成序对、两个取出前后元素的方法就行,SICP展示为了完全用过程表示数据的方式,让人大吃一惊,我们关心的是可以真正触摸产生效果的接口,一切无法观察的东西对我们来说都是没有意义的(见“看不见的龙”小故事),这个世界充满着一个规律,世界上所有物体都是透过其可以对外界产生作用的接口让外在得以察觉认识,我们知道的只是这些接口,而内部的真理却无法窥见。
可以言归正传了,看看SICP习题2.6,整个SCHEME建立在唯一一条规则上:lambda演算。发明者是著名数理逻辑学家alonzo church,已经证明了lambda演算是图灵完全的,所以一定程度上讲基于lambda演算的scheme和基于机器模型的C语言计算能力是完全等同的。C语言中数据和操作是严格区分的,静态的数据和动态的操作在我们脑海中已经根根深蒂固,但根据以上的论述,我可以想到,只要能发明一个东西,能完全满足数字的定义,那这个东西就可以完全代替数字了(本质上就是),lambda演算还真有这个能力,用操作实现数据。以下一些概念可能牵涉到数学中的实分析(推荐一本书《陶哲轩实分析》,大家可以看看如何用基本规则构成复杂的数学大厦),如果没有实分析理论基础,看数据抽象这块会有一些障碍。SICP中的例子非常简单,要大家用lambda演算建立(整数)自然数体系及自然数加法规则。
它先定义了数字0:
(define zero (lambda (f) (lambda (x) x)))
然后定义了自增1操作:
(define (add-1 n)
(lambda (f) (lambda (x) (f ((n f) x)))))
很显然有了这两个规则,其他自然数都可以定义出来了:
1 = (add-1 zero)
2 = (add-1 (add-1 zero))
3 = (add-1 (add-1 (add-1 zero)))
...
这样表示不够直接,换成如下:
(define one (lambda (f) (lambda (x) (f x))))
(define two (lambda (f) (lambda (x) (f (f x)))))
(define three (lambda (f) (lambda (x) (f (f (f x))))))
大家看见了吧,任何自然数被都定义成过程,我们再在这些过程上定义等同的四则运算,然后提供转换到阿拉伯自然数空间的方法,就建立了属于自己的自然数体系了。这里SICP让我们定义一个基于lambda自然数定义的原生加法,很简单,a + b的加法效果只需要等同于0自增a加b次就算成功了,为了更简单的检查结果到底准确与否,先实现一个lambda自然数体系到阿拉伯自然数体系的简单方法:
(define (print-x n)
(define (mark x)
(display "0"))
((n mark) 0))
使用方法如下:
(print-x three) => 000
会打印3个'0'字符,这样我们就知道这个lambda数到底对应于自然数3了,这和原始人在还没有数字概念的情况打绳结计数是一个道理:)
加法定义如下:
(define (lmd-add a b)
(lambda (f) (lambda (x)
((a f) ((b f) x)))))
测试下对不对:
(print-x (lmd-add two three)) => 00000
(print-x (add-1 (add-1 (add-1 (add-1 (add-1 zero)))))) => 00000
有兴趣的朋友还可以在这个基础上添加负数定义和加法运算。
scheme能用一条规则,推导出数字、四则运算符,甚至条件语句、循环语句等等,够原始吧,原来在C语言中被我们看来是原子的数字、运算符、流程控制关键字都可以继续分解为更细的粒度,例子中数字、加法,都只不过是lambda演算漂亮的语法糖衣(正如matrix 1中,最后neo看透了世界的外表,发现世界其实都是code)。
lisp不会衰落,只能会进化,因为他是数学家弄的一套数理逻辑,一个图灵等价的lambda演算,更直白一点讲就是用一种更简洁的方式定义了图灵机。更深层次上lisp代表了一种分析问题解决问题的思想,它和C语言所基于的数学原理从最本质上来讲是完全一样的,只是形式不同,scheme要强力依赖递归形式,其核心思想就是用简单的规则递归组合出复杂规则,将复杂现象分析抽象成简单规则的递归,这非常类似于人类大脑分析现实问题的模式(如果大家非常感兴趣可以参看一本获普利策大奖的科普书《GEB一条永恒金带》),这与类C的命令式语言强调模块治理的理念还是有很大差别的。我觉得lisp思想和分形很像,其大量用于复杂问题求解上,比如编译、定理证明等,当然问题的层次化和模块化与事物简洁规则抽象上并不冲突,相反这两种思想要在紧密配合才能将处理信息最小化,解决问题方式最优化。另外lisp给我一个明显的提醒,没有良好数学基础的程序员走不了多远(大家可以看看《编程本质》这本书,相信大家会理解这个观点)。
《量子物理史话》小故事:
“我的车库里有一条喷火的龙!”卡尔.萨根这样声称。“太稀罕了!”他的朋友连忙跑到车库中,但没有看见龙。“龙在哪里?”“哦,”萨根说,“我忘了说明,这是一条隐身的龙。”朋友有些狐疑,不过他建议,可以撒一些粉末在地上,看看龙的爪印是不是会出现。但是萨根又声称,这龙是飘在空中的。 “那既然这条龙在喷火,我们用红外线检测仪做一个热扫描?”“也不行。”萨根说,“隐形的火也没有温度。”“要么对这条龙喷漆让它现形?”--“这条龙是非物质的,滑不溜手,油漆无处可粘。”反正没有一种物理方法可以检测到这条龙的存在。萨根最后问:“这样一条看不见摸不着,没有实体的,飘在空中喷着没有热度的火的龙,一条任何仪器都无法探测的龙,和‘根本没有龙’之间又有什么差别呢?”。