第3. 一切皆對象和面向?qū)ο蟮睦碚摶A(chǔ)

老莊是反對一切皆對象的,而TrustNo1在javaeye的一篇帖子上說:

第一,我可以很負(fù)責(zé)的說,OO的,70年代成型,80年代在理論基礎(chǔ)上就給人斃掉。從這種意義上說不是OO死不死的問題,而是OO還活著么?當(dāng)然理論基礎(chǔ)給人斃掉,不是說沒有用。

我先說面向?qū)ο蟮睦碚摶A(chǔ)的問題,至于一切皆對象稍后再表。

所謂面向?qū)ο蟮睦碚摶A(chǔ)其實(shí)是沒有的,原因很簡單,面向?qū)ο蟾揪筒皇且环N計(jì)算模型。在第一次軟件危機(jī)的那個時代,對與計(jì)算機(jī)的非數(shù)值計(jì)算應(yīng)用的討論以及對于可計(jì)算性問題的研究和發(fā)展,大抵確立了幾種主流的計(jì)算模型:遞歸函數(shù)類,圖靈機(jī),Lambda演算,Horn子句,Post系統(tǒng)等等。

其中遞歸函數(shù)類是可計(jì)算性問題的數(shù)學(xué)解釋;圖靈機(jī)是圖靈解決可計(jì)算問題的時候所設(shè)計(jì)的裝置,其后成為計(jì)算機(jī)的裝置模型,與圖靈機(jī)相關(guān)的形式語言和自動機(jī)成為了命令式語言的理論基礎(chǔ);lambda演算成為了函數(shù)式語言的理論基礎(chǔ);Horn子句是prolog這類邏輯語言的理論基礎(chǔ)。但是我們驚訝的發(fā)現(xiàn),面向?qū)ο鬀]有計(jì)算模型的理論基礎(chǔ),換而言之,面向?qū)ο蟾揪筒皇菑目捎?jì)算性的研究上發(fā)展過來的,那么面向?qū)ο蟮睦碚摶A(chǔ)的價(jià)值本身就不大。

所以我很奇怪的一個問題就是TrustNo1所謂的面向?qū)ο笤?0年代理論基礎(chǔ)上給人斃掉的說法是從何而來的?既然面向?qū)ο蟊举|(zhì)上不是一種計(jì)算模型,那么它大抵上只能歸結(jié)為一種應(yīng)用技術(shù),應(yīng)用技術(shù)自然可以從各種不同的領(lǐng)域里得到相似的應(yīng)用,那么斃掉的理論基礎(chǔ)所指的又是什么呢?甚怪之。

既然面向?qū)ο蟛皇且粋€計(jì)算模型,那么我們可以從不同的角度推斷出OO的各種形態(tài),老莊已經(jīng)出給了從ADT引出OO的問題以及例子,我就不羅嗦了,我給一個從Functional Programming出來的例子,其實(shí)就是SICP里的Data as Procedure。

(define (make-user name age sex)
  (define (dispatch message)
     (cond ((eq
? message 'getName) name)
           ((eq? message 'getAge) age)
           ((eq? message 'getSex) sex))
           (else (error 'messageNotUnderstand))))
  dispatch)

然后我們就可以

(define vincent (make-user 'Vincent 24 'Male))
(vincent 
'getName)

自然的,如果我調(diào)用

(vincent 'sayHi)

會得到一個messageNotUnderstand的runtime錯誤,這就是一個很自然dyanmic type的對象封裝,最早的面向?qū)ο笙到y(tǒng)Smalltalk和CLOS基本上都是這個路子,于是有一個問題,為什么最早的面向?qū)ο笙到y(tǒng)都是dyanmic type?這里就跟lambda演算有關(guān)了。

lambda演算這個計(jì)算模型根本的一個假設(shè)就是,對于任何一個定義良好的數(shù)學(xué)函數(shù),我都可以使用lambda抽象來表述他的求值,因此無論是什么東西你能夠構(gòu)造lambda抽象的話,我就能計(jì)算。這個地方東西很多,大家可以找找lambda演算相關(guān)的資料,這里我說三件事(由于lambda太難輸入,我用scheme程序代替,然后由于alpha變化,beta規(guī)約和eta規(guī)約我也用scheme偽碼來模擬。)

第一個是數(shù)值的表述,其實(shí)這個很簡單,不考慮丘奇代數(shù)的系統(tǒng)的話,我們可以把數(shù)值表示成單值函數(shù):

(define one (lambda (x) 1))

這個東西無論給什么x都返回1,然后根據(jù)lambda演算里的alpha變換,這個lambda抽象等價(jià)于數(shù)值1。因此,對于所有的數(shù)值,我們可以按lambda演算處理。

第二個是bool的表達(dá),也就是如何邏輯進(jìn)行l(wèi)ambda抽象,下面我直接給出了,缺省認(rèn)為大家都看了SICP,對Scheme也頗有心得。

(define true-new (lambda (x y) x)) ;;;這個函數(shù)也叫select-first
(define 
false-new (lambda (x y) x));;;這個函數(shù)也叫select-second

(define 
if-new (lambda (conditon if-true if-false) (condition if-true if-false)))

然后我就可以做一個測試

(if-new true-new 3 4)
3

(
if-new false-new 3 4)
4

因此,對于所有bool我們可以按lambda演算來處理

第三個是自定義類型,這里我們還是先看一個Lisp里的例子,序?qū)Α?/P>

(define (cons a b) (lambda (dispath) (dispatch a b)))
(define (car list) (list select
-first))
(define (cdr list) (list select
-second))

這里依舊是high-order,我們來測試

(define list1 (cons 1 2))
(car list1)
1
(cdr list1)
2

這里大家自己用beta規(guī)約算一下,就發(fā)現(xiàn)的確是這樣的。這里我們又可以看到,在lambda演算里,根本沒有數(shù)據(jù)或者類型。有的永遠(yuǎn)各種各樣的lambda抽象而已(目前已經(jīng)有了帶類型的lambda演算)。這里說一句題外話,SICP里的Data as Procedure其實(shí)就是在說這個問題,不過他沒明說,而是用了一種特殊的用法而引出了消息傳遞風(fēng)格,我覺得這里SICP有些顧此失彼,對于data as procedure我總結(jié)的一般形式是

(define (construct-function value1 value2 value3 value4valuen) (lambda (dispatch) (dispatch value1 value2 value3 value4valuen)))
(define (select
-function1 data) (data select-first))
(define (select
-function2 data) (data select-second))
.
(define (select
-functionn data) (data select-last))

綜上所述,我們看到在lambda演算里,一切都是lambda抽象,然后對于不同的lambda抽象使用alpha變換,beta規(guī)約和eta規(guī)約,表述各種不同計(jì)算。看,在面向?qū)ο笾熬鸵呀?jīng)有了一切皆某某的完美的計(jì)算理論存在了。而且回顧一下:

(define (make-user name age sex)
  (define (dispatch message)
     (cond ((eq
? message 'getName) name)
           ((eq? message 'getAge) age)
           ((eq? message 'getSex) sex))
           (else (error 'messageNotUnderstand))))
  dispatch)

(define vincent (make
-user 'Vincent 24 'Male))
(vincent 
'getName)

我們有理由說,對象其實(shí)就是一個lambda抽象,所以一切皆對象不過是一切皆lambda抽象的演化,這也是為什么SICP里把面向?qū)ο蠓Q作一種“方便的界面”而不是一種抽象的方法的原因了。那么對象和lambda抽象又什么區(qū)別呢?

嘿嘿,熟悉FP的人都應(yīng)該知道了,就是Side-Effect,副作用。對象允許對其內(nèi)部狀態(tài)進(jìn)行修改,那么這個東西就破化了eta規(guī)約的前提條件,也就是說允許修改內(nèi)部狀態(tài)的東西,已經(jīng)不是一個可以進(jìn)行l(wèi)ambda計(jì)算的lambda抽象了。因此暫且給一個別的名字吧,就叫對象吧.....因此我認(rèn)為,對象很大程度上是一種帶有副作用的lambda抽象。

我在有一篇blog上寫了Say sorry to object-oriented,里面給了一只用對象作分支的例子,這里就不重復(fù)了,有興趣大家可以去看一下(剛才好像在JavaEye上看到一個說法,說Everything is Object是Smalltalk的廣告語,唉,Smalltalk里的的的確確everything is object啊。)

這里我們來總結(jié)一下,面向?qū)ο笞鳛橐环N“方便的界面”主要解決了一個局部化和模塊化的問題,這是從lambda演算和函數(shù)編程的角度來看面向?qū)ο蠹夹g(shù)。(taowen在他的一篇blog上說,面向?qū)ο缶植炕薙ide-Effect,我深以為然),這個東西我個人認(rèn)為更加接近面向?qū)ο蟊緛淼囊馑迹皇怯葾DT里發(fā)展出來的帶類型的對象系統(tǒng)的那個意思。因此老莊不以為然的面向?qū)ο箢愋拖到y(tǒng),我也不以為然。但是面向?qū)ο笞鳛閘ambda抽象的界面,我覺得還是很不錯的。這一點(diǎn)在Smalltalk和Ruby里都有不錯的體現(xiàn)。