Showing posts with label 计算机科学. Show all posts
Showing posts with label 计算机科学. Show all posts

September 22, 2007

基于消息传递的 Scheme OOP

续上次的OOP 诡异教程,我用 Scheme 宏写了一个类似的系统,拿来晒晒。

;; 这里只有部分定义,快速排序和二分法查找的代码就不帖在这儿了
;; 这里是 slot 类型的定义,Scheme 中不能动态 eval,只能用点别的招数
(define (make-slot name act) (cons name act))
(define (slot-name s) (car s))
(define (slot-act s) (cdr s))
(define (slotstring a) (symbol->string b)))

;; 主要的转换宏,语法比较丰富,但至少要带有一个 that 块
;; 语法可以只给出 that 块:(class (that (slot-name1 slot-act1) ...))
;; 也可以指定继承,要求在 class 关键字后出现原型对象:
  ;;(class extended-obj (that (slot-name1 slot-act1) ...))
;; 在 that 块之后还可以加入 where 块,这样可以指定不对外可见的类属性
;; 需要注意的是,where 块中的定义不能引用 self 等特殊命名,that 才行
;; 另外外部只能引用 origin, has-slot?, slot-names 3个特殊命名
(define-syntax class
(syntax-rules (that where)
 ((_ that-block)
  (class (absobj) that-block))
 ((_ org that-block (where def ...))
  (letrec (def ...) (class (absobj) that-block)))
 ((_ org (that (slot val) ...))
  (lambda ()
   (letrec ((slot val) ...)
   (let* ((origin org)
       (slots (list-qsort slotvector (map slot-name slots)))
       (slot-acts (list->vector (map slot-act slots)))
       (has-slot? (lambda (v)
             (vector-bsearch symbol index -1)
           (vector-ref slot-acts index)
           (case verb
            ('origin origin)
            ('slot-names slot-names)
            ('has-slot? has-slot?)
            (else (origin verb))))))))
    self))))
 ((_ that-block where-block)
  (class (absobj) that-block where-block))))

;; 这里需要注意的是,因为嫌烦,absobj 被实现为了一个空壳子
(define (absobj)
(lambda (verb)
 (display "This object can't handle ")
 (display verb)
 (newline)))

测试一下:
> (define c1 (class (that (x 10))))
; no values returned
> (define c2 (class (c1) (that (z 16) (y 12))))
; no values returned
> (define o2 (c2))
; no values returned
> (o2 'x)
10
> (o2 'non-slot)
This object can't handle non-slot
#{Unspecific}
> (o2 'slot-names)
'#(y z)
> (o2 'origin)
#{Procedure 8647 (self##497 in c1)}

输出好像很古怪的样子~`我用的环境是 Scheme 48 虚拟机
这儿的例子只覆盖了一小部分,剩下的比如 where 就自己慢慢玩吧。
Scheme 的“卫生宏”不但强大,还能制止你设计不好的语法,有意思。
消息传递真的很有意思。留个小题目,增加一个特殊方法 clone,把继承机制调整为差异继承+纯基于对象,类似 IO 语言 的面向对象机制。
这个“系统”的完整代码在此下载,升级版本就算了吧 :)

June 12, 2007

胡侃:面向对象思想的进化

本文作者还是那个无名小辈 lichray。他在考查了一些语言和历史之后,觉得有必要谈一谈自己对面向对象思想的一些诡异的想法。文中会提到许多编程语言,不过当然了,重点在于思想,文章不是用来推销语言的。


面向对象编程思想的提出已经不是几年而是几十年了,考查其思想的变化,一方面是对现有语言的一些评判,另一方面,也算是对前辈计算机科学家的缅怀。 ——题记

Kristen Nygaard在1962年发明的 Simula 语言现在被认同为世界上第一种明确实现面向对象编程中某些“必要”元素(比如 class)的语言。Simula 是从 Algol 发展来的,可以说,是一种增加了 class 这个数据类型的 Algol,并将参数传递的默认模式从“按名调用”换成了“按引用调用”,还提出了根据类型确定初始化过程的方法。
从 Simula 的时代开始,科学家们在解决软件复杂度方面的思路开始“异常开阔”——当然也有资深的老派牛人们不这么认为。比方说 Peter Norvig,写了[url="http://norvig.com/design-patterns/"]Design Patterns in Dynamic Programming[/url]一书来反驳。他认为设计模式早已体现在以 Lisp 为首的一批语言中了,根本不需要什么面向对象。但我还是跟从偶像 Alan Kay 的观点。某些时候,把一种特定的编程风格做进语言里也是必要的。
言归正传。Simula 之后的语言开始试图把 class 作为编程的基础,并提出了一系列出色的、基于类的编程风格,最终确定了“万物皆对象”这一面向对象理论的“终极思想”。于是,Alan Kay 的 Smalltalk 在 70年代诞生了。它是世界上第一个真正把面向对象作为程序组织基础手段的编程语言。它首次明确实现了“消息”和“继承”这两个重要概念,对于“封装”和“多态”也给出了里程碑式的解决方案。在 Smalltalk 程序中,一切程序元素(除了词法元素)都是对象,一切操作都是消息。而且,Smalltalk 的实现本身就是用 Smalltalk 写成的,这也就意味着,对于一个 Smalltalk 程序来说,它的底层也是以面向对象为基础的。直到现在,也只有 Self 语言有资格在面向对象的程度方面与 Smalltalk 一较高下。
Smalltalk 在成为里程碑的同时,也成为了分水岭。Smalltalk 之后的面向对象语言的发展方向分成了两条路:一是继续把 Smalltalk 中封装的概念扩大化,为程序员添置更多的约束,以支持所谓的“大规模开发”的需要;另一方面,一些人主张继续理解面向对象的原理,为这一思想添加数学上解释,而不仅仅是“拿来主义”,使程序员更不易出错或“分神”。
正像大家所猜测的那样,前者指的是以 C++ 为代表的、坚持以命令式语言风格去实现面向对象的“有类”语言,后者是以 Self 为代表的、准备以函数式语言风格去理解面向对象的“无类”语言。
有类语言中,C++ 是曾经的“蔚然大宗”。它最初的名字是 C with class,很显然,一开始只是想对 C 作出像 Simula 之于 Algol 那样的修改。但是,C++ 诞生的时候(1983年),也是 Smalltalk 80 产生的时代,也是 Ada 语言正在开发的时代。在享受了 Smalltalk 提出的类方法、对象方法、私有方法等等语言设施之后,C++ 提出了3个为后来的有类语言广泛采用的关键字:private,public,protected。不避讳的话,可以简称3P。这种区分相对于 Smalltalk 来说略少了一点,但事实证明,在实际的抽象中是够用的。另外,C++ 还从 Algol68 (这只是一个失败的旧事物罢了)那儿学来了运算符重载,还有 Ada 中的泛型和程序包。堆砌了太多特性的 C++ 显得有些不堪重负,而且失败的教育(总是拿它当 C 的升级版讲解!)、失败的实现(尤以 Visual C++ 为甚)也是它后来在开源界不那么受欢迎的重要原因。C++ 之后的有类语言开始了消减多余特性,理清思路的努力。
紧接着 C++ 之后出现的语言主要有 Objective Pascal 和 Objective-C。后者是 Mac 公司的拳头开发语言,前者是什么?就是 Turbo Pacal 中的 Pascal 语言。二者试着给古老的语言们添加新的元素。前者只是很客气的添加了 new 和 unit,特性不足;后者的转型是成功的,但说句题外话:想象一下 C 的基础上直接加上 Smalltalk 的消息传递语法,这就是 Objective-C 的语法设计。
Java,原名 Oak。这个名字现在已经处于“无敌”状态,去年已经在语言排行榜上 K 掉 C 当上了老大。Java 没有什么新东西,正如上文所说,只是一个消除了多余特性后的产物。成功的原因,一是时代的召唤,而是官方实现的基于虚拟机的跨平台特性。虚拟机早已不是什么新事物了,不过有大公司出来宣传确实是新鲜事。也许是受了微软的影响,大多数普通程序员更愿意那种喜欢被某个大公司“罩着”的感觉,于是纷纷用起了 Java。历史就是这样,听不到你的嗟呀。
Java 火起来了,各大计算机方面的媒体上充斥着 Java、OOP 等等的字眼,仿佛面向对象只剩下 Java 一种模式。各种语言纷纷改头换面,以适应“新时代”的要求:Perl 加上了4P(package 也算一个);Objective Pascal 摇身一变成了 Boject Pascal,再改名 Delphi;Basic 名字前加上了 Visual,后来后面又加了 .Net;C++ 把 ++ 改成了 # 号;就连一向对面向对象嗤之以鼻的 PHP 都被迫升到了 4.0、5.0;我的“母语”Action Script”版本也升到了2.0、3.0。
在有些人眼中,这是一场“意义重大”的变革;但在我眼中,这是一场灾难。
因为,在某个角落,无类语言们正在为寻找面向对象的数学理论基础而努力。为什么要寻找数学理论基础?这个问题早在70吗=年代就已经不是个问题了,而现在居然还有人不明所以。有类语言和无类语言的区别,就像是命令式与函数式之间的区别,就像是图灵机和 Lambda 演算之间的区别,就是一字一次的编程和面向整体操作的区别,就是 Bug 多和少之间的区别,就是单线程与高效并发之间的区别,就是必将灭亡的旧事物和必将走向辉煌的新事物之间的区别……这么多,还不够吗?
无类语言从已有的 lambda 演算理论中寻找适合解释面向对象思想的部分。1986 年研究完成的 Self 语言首先抛弃了 class 关键字,从对类和对象这两个基本问题上做文章。“万物皆对象”,类就是对象,但用它可以产生对象;怎么产生?通过复制已有对象产生。这种手段称为“基于原型的面向对象程序设计”。Self 之于无类语言,相对于 C++ 之于有类语言。它是无类语言中的蔚然大宗。但可惜的是,Sun 公司的 Self 实现几乎没有进步;Smalltalk 可能因为受够了 Java 之流自称继承了自己,认为 Self 是它唯一的“知心朋友”,发起了一个叫做 Morphic 新体系,算是对经典的延续。
Self 之后,1993年出现了 Lua。Lua 是无类语言的一个特别“函数式”的版本,在继承了 Scheme 所有思想之后,消灭了专有 List 这一数据结构,全以哈希表代之,并出色地解释了许多程序表示上的一些不太明朗的问题,还区分了消息发送和普通函数调用。Lua 不论从那个角度(科学或者商业)来看都是成功的,是无类语言中的佼佼者。
Lua 之后出现了 JavaScript,原名 LiveScript(1995),国际标准收录名为 ECMAScript。JavaScript 与 Java 诞生于同一时代,生不逢时的同时又生而逢时。说它生不逢时,是因为在 Java 的盛名之下,不负众多程序员的“众望”,被他们指责为“假面向对象”;说它生而逢时,是因为,它总算没在一浪高过一浪的 class + 4P 的嚷嚷中倒下,成为我们最为熟知无类语言。它实在是太优秀然而又太谦虚了:它秉承了 Lisp 家族的一贯传统,能够用数据表示程序本身(JSO);它又有足够的函数式编程特性,但它谦虚地称之为 function;它用复制对象再用 new 关键字 apply 构造函数的方式漂亮地解释了类和对象的关系;相对于 Lua 取消了消息发送和函数调用之间的界限;在没有任何 4P 关键字的情况下还能用逃逸变量理论实现 4P 的所有特性(不要被 dojo 之类的库蒙蔽了双眼)。几乎是一个完美的 Self 的继任者,但还是那句话,真正会 JavaScript 的人不多啊。
至此,无类语言的发展似乎已经很令人满意了,理论似乎已经成熟,只剩下一个问题:函数本身是一个什么样的对象?E 语言(1987,提出很早,实现太晚)回答了这个问题。答案很简单。函数是对象的一种,对象和方法都是 lambda 算子,如果你愿意,又可以把函数的函数体再表示为对象的一个方法!看起来这个答案不那么起眼,但它确实让人们领教了 lambda 演算在抽象层次上的嚣张。同时,E 语言还大胆地加入了对象继承的概念——一个对象可以像类继承一样继承其它对象的属性——因为这仅仅是 lambda 算子的扩展罢了!
但是,无类语言的进步不会就此结束,还有更多、跟有力量的无类语言产生,比如 Scala,一个给无类语言添加了 ML 中的静态多态类型判定和惰性运算的变态……
历史就是这样一个聋子,听不到你的嗟呀。不过,但愿后人会从这些嗟呀中借鉴到很多。

注:还有两个很牛语言没提到:Ruby 和 Python。其中前者学 Smalltalk 学地精神分裂,去掉了 Smalltalk 的强大 IDE 之后居然一炮走红;Python 是个不知道该站在那一边的语言,既没有 3P 又知道逃逸变量或 block 是什么,惨兮兮的。

May 19, 2007

读《ACM图灵奖演讲集》有感

最近我在读一本似乎和编程没太大关系的计算机书籍,叫《ACM图灵奖演讲集 - 前20年》(以下简称《ACM演讲集》)。这本书收录了1966-1985年这20年间图灵奖获奖者们的演讲。相对于计算机的普及速度,文章已经很古老了,中文版出版也很久了,但并不像那些纷扰而嘈杂的电脑书刊那样稍纵即逝。普通的电脑书,充斥着看似先进的技术;普通的编程书,充斥着不知为什么而生的代码。而它,就像一位智者,静坐在书店那些已经满是垃圾文字的书架上,看着“畅销书排行榜”上的风云变幻,嘴角一丝不易察觉的坏笑。
“看一本好书,就是在和许多伟大的灵魂对话”。如果《ACM演讲集》仅仅保存着一位作者的灵魂,不可能被我如此看重。事实上,它收录了23位获奖者的22篇文章。代码寥寥无几,甚至是作为反面教材的多于实例;除了一两篇纯属学术论文的文章,余者也没有复杂的数学推导。然而就是这样一本似乎只是在空谈的书,让我了解到了计算机科学发展的黄金时代的情况和“上古”的计算机前辈科学家们的不朽的思想与伟大的探索。约翰·巴克斯(BNF范式设计者、Algol语言发明人),唐纳德·E·克努特(《计算机编程艺术》作者、Tex系统设计者),约翰·麦卡锡(人工智能先驱,Lisp语言发明人),看着这些熟悉的名字,阅读着他们充满智慧和神性的演讲稿,研究前辈们思想的结晶,真是乐事。
我对当代计算机的发展(尤其是国内)失望久矣:教育者们误人子弟,学生们急功近利,程序员们心浮气躁,整个国内软件业不成体统。步入计算机科学的殿堂后,我头脑中一直盘旋着许多疑惑——计算机科学是怎样发展的?科学家们都是怎样探索的?我应该如何学习?《ACM演讲集》给了我答案。其中每一篇文章都在总结前人努力的基础上打开崭新的视界,向我展示了前辈们创世纪般的工作。事实上,80年代之后,商业的发展严重阻碍了计算机科学的发展以及编程语言的进步,半个世纪前的文章仍然有着重要意义,人们仍然可以窥见大师们惊人的高瞻远瞩。

May 18, 2007

推荐一本是人都知道的书

这本书就是:The C programming language

首先纠正一个误区——它决不是什么“入门书”,用它确实能入门,但更重要的是掌握其中每一句话、每一段程序的思想。CPL之于C,就好比SICP(计算机程序的构造和解释)之于Scheme,每一行都是很多计算机科学家思想、经验的结晶。
CPL的语言就是C的规范性文档,CPL的程序就是C库和UNIX系统的标准程序,你通过学习可以了解C的惯例、算法、数据结构、系统编程、内存分配、编译原理、模式识别等各项计算机科学的基础知识,引领真正的学习者选择前往计算机殿堂的路。
我希望每个学习计算机编程的人都去阅读SICP,每个学习C语言的人再去学习The C programming language。