日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

程序语言的自我意识与仿他意识

發布時間:2023/12/20 235 豆豆
生活随笔 收集整理的這篇文章主要介紹了 程序语言的自我意识与仿他意识 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

注:本篇博文為重發稿件,“CSE腳本世界”曾被清空過,現已找回原文。


這本寫于兩年前(注:指2006年),網上早就盛傳開來,當然,大部分轉載都把我的大號隱掉了,標為“來源:互聯網,作者:佚名”。這里重發一下,一是因為當時寫這篇文章確實花了些時間,還修訂過幾次,二是為了正本清源,本文乃Wayne chan所作,有人想轉載請標明出處。

一年前有人將本文推薦給某大學的學術期刊,有編輯找到我,問我想不想在他那里發表,我尋思,愿發就發吧,本人不圖稿費,也樂于為人捧場。然后這個扯著嘶啞口音的編輯給我指出一些文字上要改進的地方,我一一記下來了,末了,老先生告訴我,如果想發表,請交xx元費用。我的熱心勁一下子消失了,禮貌性回絕了他的要求,這世道怎么老跟錢掛鉤,不要稿費也就罷了,怎么還拉贊助呢?他們期刊的質量想必好不到哪兒去。


程序語言的自我意識與仿他意識

2006-4-8, by wayne chan

?

從浮點數說起

昨天有一位網友問我一個問題,為什么表達式“1.9 + 2.3 == 4.2”在Python中計算結果是FALSE?我回答說,計算機模擬浮點數時會失真,網友說這個他明白,但為什么“1.9 == 1.9”結果卻是TRUE,機器看1.9也同樣失真呀?實際上,兩個表達式是有差別的,前者在比較之前運算了,而后者沒有。

這是一個很有趣的問題,如果我們把電腦看成有感知的生物,上述問題反映了人腦與電腦是按不同方式認識事物的。首先,電腦總是以2進制表達數值的,而人腦習慣使用10進制,比方被人腦標識為0.125的數據,到電腦中就被看作0.001,即“0/2 + 0/4 + 1/8”。這兩種方式都能唯一表達浮點值,本例中,兩個浮點值能在10進制與2進制之間順利轉換,但不幸的是,大部分10進制浮點數無法準確的轉化成2進制,比如上面的1.9,在Python程序中,它顯示為1.8999999999999999。當然,這個數值還不是電腦自己認為的值,它經過兩次轉換,第一次是我輸入1.9后,電腦把它轉換成它認識的2進制格式,第二次是電腦要告訴我它接收到的值是多少,又把2進制數據轉化成10進制打印出來。

其次,電腦總嘗試以最精確的形式表達一個數據,而人腦往往以簡單形式表達復雜數據。比方,老板告訴你,“好好干活,這事成了分你三分之一利潤”,如果老板長一個CPU腦袋,又很有偏執狂精神,他可能告訴你,“好好干活,這事成了分你0.3333333333333333...”,幸好本人還不太偏執,否則,我一輩子不用干別的,把這句話轉述完就安息吧。

在Python中計算“1.0 / 3”得到結果值是0.33333333333333331,這是32位電腦對現實認知的最精確形式,而我們拿“1/3”來表達,為什么既省事,又精確?這里“1/3”不只是數值,還隱含了一種計算:先3等分再取走1份。之所以精確,因為它是自描述的,我們還原了它的本來面目,就像宋玉在《好色賦》中形容東家美女,“增之一分則太長,減之一分則太短”,如果提高浮點精度,不論32位、64位,還是128位CPU,存貯1/3最終都要截除尾數,都無可避免的要失真。

我們先確立兩個概念,其一,某種形式化的計算過程,等價于靜態數據;其二,自我描述的數據可以不失真。

表達式是不是數據?

1/3是自描述的數據,同時它還是一個可計算的表達式,我們推而廣之,是不是所有表達式都是數據?這是一個見仁見智的問題,應該沒有標準答案。

應用程序本來就是對現實世界一種模仿,編程語言只是實施模仿的輔助手段,要不要把表達式看成數據,完全在乎你——編程主體,以及當前依賴的環境——開發工具,需要采用哪種方式會更好達成目標。這句話有兩層含義,首先,編程環境可以影響一個表達式該不該成為數據,比如,在C語言中,表達式是表達式,它最終編譯成運行代碼,數據是數據,編程中可直接讀寫。而在LISP語言中,表達式也是數據,既用于運算,也支持直接讀寫。此外第二層含義,即使開發語言很大程度上決定表達式該不該成為數據,卻不絕對,還與應用相關,比如某些防跟蹤軟件,即使用C語言開發,也局部的將運行代碼看成數據,運行中動態修改,讓它在反編譯工具變成花指令。

表達式是不是數據,是函數式編程(Functional Programing,FP)區別于命令式編程(Imperative Programming)的重要差異,也是根本性差異。按嚴格定義,函數式編程把程序的輸出定義為其輸入的一個數學函數,只有把函數調用也看成數據,中參數中層層串接,最后才組裝出完整的程序。比如:if..else句式,使用函數式語言表達如下:

else(if(expr,TRUE_branch),FALSE_branch);

外層調用else函數時,先計算第一個參數(即if函數調用),如果if條件成立,執行TRUE_branch語句并返回TRUE,否則直接返回FALSE,接著else函數分析if調用的返回值,決定FALSE_branch語句是否要執行。這個例子我們看到,把函數用作參數,可以連接多個運算。

按嚴格FP定義,函數式編程沒有內部狀態的概念,而命令式編程要頻繁使用賦值語句,用變量保存內部運行狀態,比方if..else在命令式語言(如C語言)中實現時,兩條語句看似無關,但實際是按固定格式框定了的,前后必定相連,計算中系統還隱式的使用臨時變量記錄if條件值。

我們常見的語言,包括C、C++、Pascal、Java等都是函數式語言,函數式語言主要有LISP、Haskell、Scheme、ML等,除了這兩者,還有一類邏輯式編程(Logic Programming,LP)文獻中通常也將它看成獨立分支。因為,大部分函數式語言都很容易構造出邏輯式程序,LP與FP實際是比較近似的。

所以,大致而言,命令式與函數式代表當前編程語言的兩大流派,本文嘗試從根源上探究兩者差異的原因,借用摩爾根(美國生物學家,1866—1945)的話,基因決定性狀,我們嘗試從基因分析入手,分析兩者內在機制的差異性,理出脈絡后,再將他們的外在差異貫穿起來講述。

高階函數與惰性求值

高階函數(High-Order function)與惰性求值(Lazy evaluation)是函數式編程的重要特征,這兩者都直接起源于運算式的形式化描述,也直接促使FP具有廣泛的多態性。

所謂高階函數是指以函數作為參數或返回值是函數的函數,比如Python中的apply函數:

apply(pow,[1.9,2])
8

pow是Python一個內嵌函數,把它當作一個參數傳給apply,apply函數運行中,先把pow及兩個參數組織成可計算對象(即Python中CodeType類型的數據),然后完成pow(1.9,2)求平方數運算。

下面,我們再看看惰性求值是怎么回事,比如我們定義一個函數handover,它的功能是把一個表達式從一臺機器傳遞到另一臺機器再計算,如:

handover(pow,[1.9,2],’Machine_B’)

惰性求值可以讓我們在需要的時候才做計算,特定場合下惰性求值能產生奇異功能。比如,本例類似于前面提到1/3,我們知道,在異構系統中傳遞數值會失真,如果我們把“1.9 ** 2”這種自描述的數據傳遞給更高精度的CPU,比起算出浮點數再傳遞,結果肯定更加精確。再如,某些樹狀結構下實現快速搜索,遍歷樹節點時我們附帶算法(當作數據被傳遞),在特定節點或特定情況下,才將它看成表達式進行計算。
讓程序操縱它自身代碼,將形成比面向對象語言更為廣泛的多態性,不只程序的伸縮性增強了,也讓軟件具備某種自我感知的能力。

比如,1971年鮑布.托馬斯編寫了一個叫“爬行者”的程序,這個程序能自我復制,能從網絡一個節點爬到另一個節點,還出乎意料的在每一臺機器的屏幕寫上“I’m creeper! Catch me if you can!”。沒過多久,整個局域網就布滿了爬行者,它們消耗很多資源。為了清理這些淘氣包,另一個程序員設計出一個叫“收割者”的程序,它可以搜尋并刪除爬行者,當系統中再也找不到爬行者時,收割者就執行程序中最后一項指令:毀滅自己,然后從電腦中消失。

這種爬行者與收割者實際上是病毒軟件與反病毒軟件的雛形,把自身代碼也當成數據來處理,就輕松實現自我繁殖。當然,自我繁殖還只是一種簡單的復制拷貝,后面章節我們將進一步討論程序語言的更高形態——自我演進。


二元世界

下面再看另一個函數式語言特征:lambda演算。形式上我們可以把lambda看作動態定義的匿名函數,比如在Python中:

>>> apply(lambda x: x * x, [3])
9
>>> iValue = 4
4
>>> apply(lambda x: x * iValue, [3])
12

lambda運算深刻的表現出FP獨有特征,實際上,lambda演算先于函數式語言就存在了,1930年Alonzo Church和Stephen Kleene為推論構造性證明與非構造性證明,建立起lambda數學基礎,后來lambda引入LISP等語言,成為函數式編程的基礎。

lambda演算克服了命令式語言的非構造性缺陷,也即,運算過程沒有告訴我們,如何獲得“針對任一輸入得到確定輸出”的計算方法。所謂構造性證明是指能具體的給出某對象,或某對象的計算方法,當能證實“存在一個X滿足性質A”的證明時,我們稱它是構造性的,反之,非構造性證明主要通過反證法實現。lambda演算中所有東西都是函數,例如自然數就可以表達成一個可分辨的零函數(通常用恒等函數表示),和一個后繼函數(用于得到下一個自然數),其構造性特征成為數理推論的基礎。

如康德所說,“數學知識是從概念的構造得出來的理性知識。構造一個概念,意即先天的提供出來與概念相對應的直觀”,后來,19世紀德國的克羅內克又進一步指出:“上帝創造了整數,其余都是人做的工作”。主張自然數與數學歸納法,是數學最根本的和直觀上最可信的出發點,其它一切數學對象都必須能在有限步驟內,從自然數中構造出來,否則就不能作為數學對象。

lambda演算完美的結合了作為表達式的動態運算特征,以及作為數據的靜態存在特征,其描述形式(指匿名申明與即時定義)非常適合推導事物的本原,下面我們換一個角度詮釋lambda的存在意義。

按照我們的生活經驗,認識一個物體首先要從靜態角度觀察,高速運動中的物體是很難識別的。編程語言中的數值、變量,以及已定義的函數,都是這一類靜態物體。另外,靜態物體若不與其它物體交互作用,就不能展現其功能,它的存在僅僅是概念,編程語言中的表達式,或者說函數調用,實際就是這種交互作用的形式化描述。lambda將兩者合二為一,如上面Python例子,“lambda x: x * iValue” 定義是上下文相關的(否則會找不到iValue), 在參數傳遞期間,lambda函數是靜態存在,以數據形成傳遞,而同時它又是匿名的,該函數并不對他人產生影響。當這種沒有副作用的演算方法,用于獲得“針對任一輸入得到確定輸出”的計算方法時,我們說它具備自我描述能力。

自描述在眾多FP語言中很容易找到例證,比如有人曾用LISP語言自身,開發出LISP解釋器,下面我們用Python腳本再舉一個淺顯例子:

def PrintEval(s,Global,Local):if s == 'exit': return 1else: try: print eval(s,Global,Local)except: from traceback import print_exc; print_exc()def test():iValue = 99dbLoop = lambda d1,d2:PrintEval(raw_input('DB>'),d1,d2) or dbLoop (d1,d2)dbLoop(globals(),locals())

本例定義一個標識為dbLoop的lambda函數,lambda函數中使用or連接它自身,由此構造出循環邏輯。調用dbLoop后系統進入交互調試狀態,循環執行用戶鍵入的命令直至輸入exit退出。這個例子盡管簡單,我們顯然做到交互執行器自我描述了。

本文中自我描述是指對于所有表達式E,在新構造的解釋器I下計算E得到的結果,與直接計算E得到的結果值相同,這不是說拿構造物替代原有功能。

受限于Python的語言能力,上述自描述代碼沒有用純粹的FP方式實現,其中PrintEval函數定義及dbLoop變量是命令式的。命令式風格讓自描述過程無法自包含,會遺留一些影響(如上例PrintEval與dbLoop定義),比如交互調試中,用戶可能修改dbLoop的屬性,而dbLoop要提供正常的調試功能,須保證它在使用過程不受影響。

按照自描述的定義,所有表達式E在自描述前后執行是等同的,所以,從嚴格意義上講,上述代碼還沒實現真正的自描述,產生該問題的根本原因是引入了命令式風格。我們把代碼改造成如下形式,可以消除dbLoop變量的影響。

def LoopEval(Global,Local):while 1:s = raw_input('DB>')if s == 'exit': returntry: print eval(s,Global,Local)except: from traceback import print_exc; print_exc()yield sdef test():iValue = 99apply( lambda d1,d2: [item for item in LoopEval(d1,d2)],[globals(),locals()] )

從理論上說,某系統如果是自描述的,它需要支持完整的lambda演算,以及,該系統中至少能找出一個不動點。大家不妨進一步練習,看看上面例子中,LoopEval函數定義是否也可消除(提示一下,請大家分析print與yield兩個命令式語句,看看能不能找到可替換函數)。

前面講到構造性證明與非構造證明,如果覺得不好理解,建議大家結合上面例子再細琢磨。命令式編程與函數式編程反映兩種截然不同的世界觀,命令式從已然經驗推導行為規則,而函數式從非確定經驗推導與源頭可能等價的規則;前者反映了一種靜態的、先驗性的認知,而后者是動態的、后天學習的、尚在成長中的認知。

命令式語言好比是無所不知、無所不能的上帝,憑他自己的能力與意愿,制造出精美絕倫的亞當與夏娃。而函數式語言則是人類自身,任何個體都不是全能的,但通過自我演進直至最終趨于完美。

還有一個比較恰當的隱喻,拿人工生命科學中常用的兩個術語概括:命令式風格是生命如我所識(life-as-we-know-it),而函數式風格是生命如其所能(lift-as-it-could-be),前者拿一種經驗套用另一種經驗,是仿他意識,而后者表現出一種自我意識。再通俗一點來講,針對同一功能實現,命令式語言會說:“這事我看如何如何...”,而函數式語言會說:“這事本來如何如何...”。


不能兩次踏進同一河流,還是一次也不能

早期算法研究表明,基于自動機、符號操作、遞歸的函數定義和組合學,都具等備等價的描述能力,盡管命令式與函數式的語言風格存在很大差異,但他們的計算能力是同等的,也即符合著名的丘奇論題:任何符合直觀理解的計算模型都具有相同的計算能力。隨著時間推移,人們已經形式化的證明了各個算法之間具有等價性,其中包括圖靈自動機與lambda演算。

圖靈機是一種由一個無限長紙帶、一個讀寫頭組成的機器,它具備訪問紙帶上任一單元的能力,由內部程序驅動不斷的修改存儲帶上各單元的值,最終完成各項運算。圖靈機是一種非常簡潔的計算模型,具備完整的計算能力,我們只要改變它的內部程序,完全可以勝任現代計算機能做的工作。

大家知道,計算程序如果有Bug可能會造成死機,圖靈機具有等價能力,它如果運行特定控制邏輯,會不會也出現類似情況呢?下面我們分析Alan Turing提出的圖靈停機問題:

存不存在一個圖靈程序,比如說P能夠判斷出任一程序X是否會在輸入Y的情況下陷入死循環?若有死循環P將返回TRUE,否則返回FALSE。
我們嘗試一勞永逸的構造出這種檢查死循環的程序P,該程序同樣可作用于自身,即,將P自身作為輸入,運行P最后上報檢查結果。如果假定P在某種輸入情況下不存在死循環,那它最終上報結果是FALSE,但此時如果P存在死循環,那它應該上報TRUE,現在問題出來了,此條件下P程序有死循環,那它能否上報結果呢?

圖靈停機是個悖論,它嘗試在P程序尚不存在,或定義尚未完成時,就要求自身能被分析,這好比一條巨兇猛的蟒蛇,它有能力吞吃世界上任一條蛇,現在我們發出指令讓它吞吃自己,于是蛇頭咬蛇尾,有一半下肚了,另一半還能不能繼續吞咽下去?!

圖靈停機反映出一個深刻問題:自我演進的系統能否始終處于自我否定狀態?GCC用來編譯C代碼,而它自身也是用C語言開發的,我們拿GCC編譯自身源碼,一次編譯造就GCC一次自我演進,把圖靈停機問題套用到GCC,就是GCC停機問題:

存不存在一個現成GCC版本,它總能順利編譯因自身語法調整而修改的源碼?

當然,答案是否定的,我們先討論一個更為廣泛的命題——不動點定理,之后回頭分析GCC停機的原因。

一維布勞威爾不動點定理:設f(x)是定義在[0,1]上的連續函數,且滿足0≤f(x)≤1,則必存在x0∈[0,1],使f(x0) = x0。

由于f(x)是連續函數,當x在0到1的區間連續變化時,f(x)曲線必然跨越下圖A區與B區,或者與分隔線(y = x)的端點重合,交叉點(或重合的端點)就是不動點x0,重合點既滿足y = f(x),也滿足y = x,即f(x0) = x0。


上面f(x)是指定區間內的連續函數,如果應用到FP編程,函數可以成為f(x)的傳入參數,也可以是它的返回值,在自我描述的系統中,Y是這樣的運算符,它作用于任何一個函數 F,就會返回一個函數X,再把F作用于這個函數X,還是得到X。即“(Y F) = (F (Y F))”,由于Y作用于F返回的X,在其操作前與操作后維持穩定,我們稱它為F的不動點。

X代表了F中的某種穩定的本質性的東西,而Y操作的目的是要發現這種本質特征。從上圖我們還可以看出,一個系統的不動點可能有多個,前面提到的編程語言等價性,不同計算模型等價性,都側面驗證了這一情況。

即使是同一編程語言,完成一次自我演化都可以憑借不同的不動點,比如,if、else語句可以用帶短路功能的與或邏輯代替,循環語句能用遞歸調用構造,如前面舉例的Python代碼,用OR連接自身后調用就構造出完整的循環處理。條件選擇與循環處理還可以用更低層次的匯編指令去實現,一個條件跳轉指令就夠了。

我們回到GCC停機問題,這個命題是說GCC“總能”編譯自身變化中的代碼,原理上說它的變化是指定區間內的連續函數,如果GCC基礎語法調整了(比方刪除if、else,改用其它表達方式),必然可能破壞已有不動點。盡管一次演化可選擇不同的不動點,但這是普遍意義上講的,針對GCC特例,本命題的前提是:當前編譯器與被編譯代碼都還是GCC,并不變成其它語言(如匯編語言),不動點不存在了,就無法支持自我重構。什么叫GCC還是GCC?請大家體會一下,這句話是不是很有嚼頭。

在一次編程中,我們把無副作用的lambda演算歸結為函數式風格,把有靜態規格定義的變量或命令語句,歸結為命令式風格。推而廣之,局部演化特征必然反映到整體,自描述系統的一次演進過程中,存在不動點可認為是命令式風格,而規格平滑演進屬函數式風格。

這種動靜結合、交互輪替的規則,遍布于有機體各個角落,及其發展進程的每一時刻。映證了辯證法觀點,運動是絕對的,靜止是相對的,而且是必須的,否則導致不可知論。比如GCC演進,按照赫拉克利特的說法,“人不能兩次踏進同一條河流”,GCC演化中仍具有暫態的穩態性狀,所以,GCC還可以看成GCC,而按赫拉克利特的學生克拉蒂魯的說法,人連一次也不能踏進同一條河流,程序經過修改就不再是GCC,GCC停機無從談起了。

無論圖靈停機,還是GCC停機,提出問題者是以靜態角度,命令方式提出質疑的,如果換成函數式思維去觀察,這兩個命題是不是還存在?

假定我們自身就是萬能的圖靈機,如果真要檢查自身邏輯處理是否有死循環,現實一點,我們會再造一臺圖靈機,讓那一臺圖靈機做檢查,因為當自身還不能運轉時,不可能檢查自身的。對于迭代中的GCC,假定我們自身就是GCC程序,哪部分改了就是修改了,如涼水澆背,冷暖自知,并不是我們非得要給個概念,前一刻叫啥,后一刻又叫啥。

下面我們輕松一下,欣賞一段禪宗公案,《大慧普覺禪師語錄.卷二一》中記錄:

僧問趙州:“狗子還有佛性也無?”州云:“無。”看時不用博量,不用注解,不用要得分曉,不用向開口處承當,不用向舉起處作道理,不用墮在空寂處,不用將心等悟,不用向宗師說處領略,不用掉在無事甲里。但行住坐臥時時提撕“狗子還有佛性也無?”,“無。”提撕得熟,口議心思不及,方寸里七上八下,如咬生鐵橛,沒滋味時,切莫退志。

(注:提撕是指佛教徒參究古公案)

注意,這里“無”并非“有”的對立面:沒有,而是踏殺了“有”、“沒有”、“有且沒有”、“有或沒有”、“非有且沒有”、“非有或沒有”...,直到言亡慮絕才悟得的道理。


總結

命令式與函數式是編程語言的兩大流派,它們擁有各自的優勢與劣勢。總體上說,命令式語言更加符合人們的思維習慣,人總是習慣以自我為中心觀察世界的,以靜態的、有差別的(一個個對象)方式認識周邊事物。而函數式語言,在某種意義上說,有點違反人性,它無時不刻都以函數為中心,用括號串接各個表達式(稱作劍橋波蘭記法),其風格讓大多數編程人員范暈。所以,單憑這一點,命令式語言占據現實應用的絕對地位并不奇怪。

事實上,真正純凈的FP語言并不多,函數式語言也往往增加命令式特征,象LISP,盡管被公認為函數式編程的鼻祖,但它還不是純正的函數式編程,在LISP中可以用let指令賦值,其它的,如Scheme、ML等都不是純正FP語言。

函數式語言除不夠友好,運算效率低下外,他的優點非常鮮明,動態定義、無副作用、良好伸縮性、強大的演進能力等,都是命令式語言難以企及的。由于命令式與函數式各有優勢,兩者走向融合是近些年來編程技術的發展趨勢,尤其隨著Python、Ruby等動態語言逐步深入人心,FP編程漸漸流行開來。目前像Java、C#等新興語言,已經融入了不少FP編程要素,借助反射等機制,命令式語言也擁有很強的動態編程特性。

?

參考資料:
  • 《Programming Language Pragmatics》,美國,Michael L.Scott著,《程序設計語言----實踐之路》,裘宗燕譯,電子工業出版社
  • 《皇帝新腦》牛津大學,羅杰.彭羅斯著,湖南科技技術出版社,許明賢、吳忠超譯
  • 《數字創世紀:人工生命的新科學》,李建會、張江編著,科學出版社
  • Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I,John McCarthy, Massachusetts Institute of Technology,April 1960,
  • Why Functional Programming Matters,John Hughes paper, dates from 1984
  • Charming Python: Functional programming in Python, David Mertz, 01 Mar 2001

  • 總結

    以上是生活随笔為你收集整理的程序语言的自我意识与仿他意识的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。