<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 第 1 章 可擴展語言 不久前,如果你問 Lisp 是用來干什么的,很多人會回答說 "人工智能(articial intelligence)" 。事實上,Lisp 和人工智能之間的聯系只是歷史的偶然。 Lisp 由 John McCarthy 發明,同樣是他首次提出了 "人工智能" 這一名詞。那時他的學生和同事用 Lisp 寫程序,于是它就被稱作一種 AI 語言。這個典故在 1980 年代 AI 短暫升溫時又被多次提起,到現在已經差不多成了習慣。 幸運的是, "AI 并非 Lisp 的全部" 的觀點已經開始為人們所了解。近年來軟硬件的長足發展已經讓 Lisp 走出了象牙塔: > 它目前用于GNUEmacs -- Unix 下最好的文本編輯器; > > AutoCAD -- 工業標準的桌面CAD 程序; > > 還有Interleaf -- 領先的高端出版系統。 Lisp 在這些程序里的應用跟AI 已經沒有了任何關系。 如果 Lisp 不是一種 AI 語言,那它是什么?與其根據那些使用它的公司來判斷 Lisp ,我們不如直接看看語言本身。有什么是你可以用 Lisp 做到,而其他語言沒法做到的呢? Lisp 的一個最顯著的優點是可以對其量身定制,讓它與用它寫的程序相配合。Lisp 本身就是一個 Lisp 程序,Lisp 程序可以表達成列表,那也是 Lisp 的數據結構。 總之,這兩個原則意味著任何用戶都可以為 Lisp 增加新的操作符,而這些新成員和那些內置的操作符是沒有區別的。 ### 1.1 漸進式設計 由于 Lisp 賦予了你自定義操作符的自由,因而你得以隨心所欲地將它塑造成你所需要的語言。 > 如果你在寫一個文本編輯器,那么可以把 Lisp 轉換成專門寫文本編輯器的語言。 > > 如果你在編寫 CAD 程序,那么可以把 Lisp 轉換成專用于寫 CAD 程序的語言。 > > 并且如果你還不太清楚你要寫哪種程序,那么用 Lisp 來寫會比較安全。 因為無論你想寫哪種程序,在你寫的時候,Lisp 都可以演變成用于寫那種程序的語言。 你還沒想好要寫哪種程序?一樣可以。對有些人來說,這種說法有點不對勁。這和某種行事方式很不一樣,這種方式有兩步: > (1) 仔細計劃你打算做的事情,接下來 > > (2) 去執行它。 按照這個邏輯,如果 Lisp 鼓勵你在決定程序應該如何工作之前就開始寫程序,它只不過是慫恿你匆忙上馬,草率決定而已。 事實并非如此。先計劃再實施的方法可能是建造水壩或者發起戰役的方式,但經驗并未表明這種方法也適用于寫程序。為什么?也許是因為計算機的要求太苛刻了。也許是因為程序中的變數比水壩或者戰役更多。或許老方法不再奏效的原因,是因為舊式的冗余觀念不適用于軟件開發: > 如果一座大壩澆鑄了額外的 30% 的混凝土,那是為以后的誤操作留下的裕量,但如果一個程序多做了額外 30% 的工作,那就是一個錯誤。 很難說清原來的辦法為什么會失效,但所有人都心知肚明老辦法不再行之有效。究竟有幾次軟件按時交付過?有經驗的程序員知道無論你多小心地計劃一個程序,當你著手寫它的時候,之前制定的計劃在某些地方就會變得不夠完美。有時計劃甚至會錯得無可救藥。卻很少有"先策劃再實施" 這一方法的受害者站出來質疑它的有效性。相反,他們把這都歸咎于人為過失: > 只要計劃做的更周詳,所有的問題就都可以避免。 就算是最杰出的程序員,在進行具體實現的時候也難免陷入麻煩,因此要人們必須具備那種程度的前瞻性可能過于苛求了。也許這種先策劃再實施的方法可以用另外一種更適合我們自身限制的方法取而代之。 如果有合適的工具,我們完全可以換一種角度看待編程。為什么我們要在具體實現之前計劃好一切呢?盲目啟動一個項目的最大危險是我們可能不小心就使自己陷入困境。但如果存在一種更加靈活的語言,是否能為我們分憂呢?我們可以,而且確實如此。Lisp 的靈活性帶來了全新的編程方式。在 Lisp 中,可以邊寫程序邊做計劃。 為什么要等事后諸葛亮呢?正如 Montaigne 所發現的那樣,如果要理清自己的思路,試著把它寫下來會是最好的辦法。一旦你能把自己從陷入困境的危險中解脫出來,那你就可以完全駕馭這種可能性。邊設計邊施工有兩個重要的后果:程序可以花更少的時間去寫,因為當你把計劃和實際動手寫放在一起的時候,你總可以把精力集中在一個實際的程序上;然后讓它變得日益完善,因為最終的設計必定是進化的成果。 只要在把握你程序的命運時堅持一個原則:一旦定位錯誤的地方,就立即重寫它,那么最終的產品將會比事先你花幾個星期的時間精心設計的結果更加優雅。 Lisp 的適應能力使這種編程思想成為可能。確實,Lisp 的最大危險是它可能會把你寵壞了。使用 Lisp 一段時間后,你會開始對語言和應用程序之間的結合變得敏感,當你回過頭去使用另一種語言時,總會有這樣的感覺: > 它無法提供你所需要的靈活性。 ### 1.2 自底向上程序設計 有一條編程原則由來已久:作為程序的功能性單元不宜過于臃腫。如果程序里某些組件的規模增長超過了它可讀的程度,它就會成為一團亂麻,藏匿其中的錯誤就好像巨型城市里的逃犯那樣難以捉摸。這樣的軟件將難以閱讀,難以測試,調試起來也會痛苦不堪。 按照這個原則,大型程序必須細分成小塊,并且程序的規模越大就應該分得越細。但你怎樣劃分一個程序呢?傳統的觀點被稱為自頂向下的設計:你說 "這個程序的目的是完成這七件事,那么我就把它分成七個主要的子例程。第一個子例程要做這四件事,所以它將進一步細分成它自己的四個子例程",如此這般。這一過程持續到整個程序被細分到合適的粒度 每一部分都足夠大可以做一些實際的事情,但也足夠小到可以作為一個基本單元來理解。 有經驗的 Lisp 程序員用另一種不同的方式來細化他們的程序。和自頂向下的設計方法類似,他們遵循一種叫做自底向上的設計原則, 即通過改變語言來適應程序。在 Lisp 中,你不僅是根據語言向下編寫程序,也可以根據程序向上構造語言。在編程的時候你可能會想 " Lisp 要是有這樣或者那樣的操作符就好了。" 那你就可以直接去實現它。之后,你會意識到使用新的操作符也可以簡化程序中另一部分的設計,如此種種。語言和程序一同演進。就像交戰兩國的邊界一樣,語言和程序的界限不斷地移動,直到最終沿著山脈和河流確定下來,這也就是你要解決的問題本身的自然邊界。最后你的程序看起來就好像語言就是為解決它而設計的。并且當語言和程序彼此都配合得非常完美時,你得到的將是清晰、簡短和高效的代碼。 需要強調的是,自底向上的設計并不意味著只是換個次序寫程序。當以自底向上的方式工作時,你通常寫出來的程序會徹底改觀。你將得到一個帶有更多抽象操作符的更大的語言,和一個用它寫的更精練的程序,而不是單個的整塊的程序。你得到將是拱而非梁。 在典型的程序中,一旦把那些僅僅是做非邏輯工作的部分抽象掉,剩下的代碼就短小多了;你構造的語言越高階,程序從上層邏輯到下層語言的距離就越近。這有幾個好處: 1. 通過讓語言擔當更多的工作,自底向上設計產生的程序會更加短小輕快。一個更短小的程序就不必劃分成那么多的組件了,并且更少的組件意味著程序會更易于閱讀和修改。更少的組件也使得著組件之間的連接會更少,因而錯誤發生的機會也會相應減少。一個機械設計師往往努力去減少機器上運動部件的數量,同樣有經驗的 Lisp 程序員使用自底向上的設計方法來減小他們程序的規模和復雜度。 2. 自底向上的設計促進了代碼重用。當你寫兩個或更多程序時,許多你為第一個程序寫的工具也會對之后的程序開發有幫助。一旦積累下了雄厚的工具基礎,寫一個新程序所耗費的精力和從原始(raw) Lisp 環境白手起家相比,前者可能只是后者的幾分之一。 3. 自底向上的設計提高了程序的可讀性。一個這種類型的抽象要求讀者理解一個通用操作符,而一個具體的函數抽象要求讀者去理解的則是一個專用的子例程。 譯者注:Montaigne,即MichelRyquemdeMontaigne。國內一般譯作"蒙田"。他是法國文藝復興后期重要的人文主義學者,他曾說過"我本人就是作品的內容"。"但是沒人能讀懂你的程序,除非理解了所有新的實用函數"。要想知道為什么這種認識是一種誤解,請參考第4.8 節。 1. 由于自底向上的設計驅使你總是去關注代碼中的模式,這種工作方式有助于理清設計程序時的思路。 如果一個程序中兩個關系很遠的組件在形式上很相似,你就會因此注意到這種相似性,然后也許會以更簡單的方式重新設計程序。 對于其他非 Lisp 的語言來說,自底向上的設計在某種程度上也是可能的。大家熟悉的庫函數就是自底向上設計的一種體現。然而在這方面,Lisp 還能提供比其他語言更強大的威力,而且在以 Lisp 風格編程時,擴展這門語言的重要性也相應提高了,所以 Lisp 不僅是一門不同的編程語言,而且是一種完全不一樣的編程方式。 確實,這種開發風格更適合那種可以小規模開發的程序。不過,與此同時,它卻讓一個小組所能做更多的事情。在《人月神話》一書中,FrederickBrooks 提出"一組程序員的生產力并不隨人員的數量呈線性增長"。 隨著組內人數的增加,個體程序員的生產力將有所下降。 Lisp 編程經驗以一種更加令人振奮的方式重申這個定律:隨著組內人數的減少,個體程序員的生產力將會提高。一個小組取得成功的原因,僅僅是因為它的規模相對較小。如果一個小組能利用 Lisp 帶來的技術優勢,它必定會走向成功。 ### 1.3 可擴展軟件 隨著軟件復雜度的提高,編程的 Lisp 風格也變得愈加重要。專業用戶現在對軟件的要求如此之多以致于我們幾乎無法預見到他們的所有需求。就算用戶自己也沒辦法預測到他們所有的需求。但如果我們不能給他們一個現成的軟件,讓它能完成用戶想要的每個功能,那么我們也可以交付一個可擴展的軟件。我們把自己的軟件從單單一個程序變成了一門編程語言,然后高級用戶就可以在此基礎上構造他們需要的額外特性。 自底向上的設計很自然地產生了可擴展的程序。最簡單的自底向上程序包括兩層:語言和程序。復雜的程序可以被寫成多個層次,每一層作為其上層的編程語言。如果這一哲學被一直沿用到最上面的那層,那最上面的這一層對于用戶來說就變成了一門編程語言。這樣一個可擴展性體現在每一層次的程序,與那些先按照傳統黑盒方法寫成,事后才加上可擴展性的那些系統相比,更有可能成為一門好得多的編程語言。 X-Window 和 TEX 就是遵循這一設計原則編寫而成的早期典范。在 1980 年代,更強大的硬件使得新一代的 程序能使用 Lisp 作為它們的擴展語言。首先是 GNUEmacs,流行的 Unix 文本編輯器。緊接著是 AutoCAD ,第一個把 Lisp 作為擴展語言的大型商業軟件。1991 年 Interleaf 發布了他們軟件的新版本,它不僅采用 Lisp 作為擴展語言,甚至該軟件大部分就是用 Lisp 實現的。 Lisp 這門語言特別適合編寫可擴展程序,主要原因是因為它本身就是一個可擴展的程序。如果你用 Lisp 寫你的程序以便將這種可擴展性轉移到用戶那里,你事實上已經毫不費力地得到了一個可擴展語言。并且用 Lisp 擴展 Lisp 程序,和用一個傳統語言做同樣的事情相比,它們的區別就好比面對面交談和使用書信聯系的區別。如果一個程序只是簡單提供了一些供外部程序訪問的方式,以期獲得可擴展性,那么我們最樂觀的估計也無非是兩個黑箱之間彼此通過預先定義好的渠道進行通信。在 Lisp 里,這些擴展有權限直接訪問整個底層程序。這并不是說你必須授予用戶你程序中每一個部分的訪問權限,只是說你現在有機會決定是否賦給他們這樣的權限。 當權限的取舍和交互式環境結合在一起,你就擁有了處于最佳狀態的可擴展性。任何軟件,如果你想以它為基礎,在其上進行擴展,為己所用,在你心中就好比有了一張非常大,可能過于巨大的完整的藍圖。要是其中的有些東西不敢確定,該怎么辦?如果原始程序是用 Lisp 開發的,那就可以交互式地試探它:你可以檢查它的數據結構;你可以調用它的函數;你甚至可能去看它最初的源代碼。這種反饋信息讓你能信心百倍地寫程序 去寫更加雄心勃勃的擴展,并且會寫得更快。一般而言,交互式環境可以讓編程更輕松,但它對寫擴展的人來說尤其有用。 可擴展的程序是一柄雙刃劍,但近來的經驗表明,和鈍劍相比,用戶更喜歡雙刃劍。可擴展的程序看起來正在流行,無論它們是否暗藏危機。 1.4 擴展 Lisp 有兩種方式可以為 Lisp 增加新的操作符:函數和宏。在 Lisp 里,你定義的函數和那些內置函數具有相同的地位。如果想要一個新的改版的mapcar ,那你就可以先自己定義,然后就像使用mapcar 那樣來使用它。 例如,如果有一個函數,你想把從 1 到 10 之間的所有整數分別傳給它,然后把函數的返回值組成的列表留下,你可以創建一個新列表然后把它傳給 mapcar : ~~~ (mapcar fn (do* ((x 1 (1+ x)) (result (list x) (push x result))) ((= x 10) (nreverse result)))) ~~~ 但這樣做既不美觀又沒效率。換種辦法,你也可以定義一個新的映射函數 map1-n (見36 頁),然后像下面那樣調用它: ~~~ (map1-n fn 10) ~~~ 定義函數相對而言比較直截了當。而用宏來定義新操作符,雖然更通用,但不太容易理解。宏是用來寫程序的程序。這句話意味深長,深入地探究這個問題正是本書的主要目的之一。 深思熟慮地使用宏,可以讓程序驚人的清晰簡潔。這些好處絕非唾手可得。盡管到最后,宏將被視為世上最自然的東西,但最初理解它的時候卻會舉步維艱。部分原因是因為宏比函數更加一般化,所以編寫的時候要考慮的事情更多。但宏難于理解,最主要的原因是它太另類了。沒有任何一門語言有像 Lisp 宏那樣的東西。所以學習宏,可能先要從頭腦中清除從其他語言那里潛移默化接受的先入為主的觀念。這些觀念中,首當其沖就是為那些陳詞濫調所累的程序。憑什么數據結構可以變化,并且其中的數據可以修改,而程序卻不能呢?在 Lisp 里,程序就是數據,但其中深意需要假以時日才能體會到。 如果你需要花些時間才能習慣宏,那么這些時間絕對是值得的。即使像迭代這樣平淡無奇的用法中,宏也可以使程序明顯變得更短小精悍。假設一個程序需要在某個程序體上從 a 到 b 來迭代x 。Lisp 內置的 do 可以用于更加一般的場合。而對于簡單的迭代來說,用它并不能寫出可讀性最好的代碼: (do ((x a (+ 1 x))) ((> x b)) (print x)) 另一方面,假如我們可以只寫成這樣: (for (x a b) (print x)) 宏使這成為可能。用六行代碼(見第104 頁),我們就能把 for 加入到語言中,就好像原裝的一樣。并且正如后面的章節所展示的,寫個 for 對宏的廣闊天地來說,不過是小試牛刀。 沒有人對你橫加限制,說每次只能為 Lisp 擴展一個函數或是宏。如果需要,你可以在 Lisp 之上構造一個完整的語言,然后用它來編寫程序。 Lisp 對于寫編譯器和解釋器來說是極為優秀的語言,但它定義新語言的方式和以往完全不同,這種方式通常更加簡潔,而且自然,也更省力:即在原有的 Lisp 基礎上加以修改,成為一門新的語言。這樣,Lisp 中保持不變部分可以在新語言里(例如數學計算或者I/O 操作) 得以繼續沿用, 你只需要實現有變化的那部分(例如控制結構)。以這種方式實現的語言被稱為嵌入式語言。 嵌入式語言是自底向上程序設計的自然產物。Common Lisp 里已經有了好幾種這樣的語言。其中最著名的 ??? 將在最后一章里討論。但你也可以定義自己的嵌入式語言。然后就能得到一個完全為你程序度身定制的語言,甚至它們最后看起來跟 Lisp 已經截然不同。 ### 1.5 為什么(或說何時) 用 Lisp 這些新的可能性并非來自某一個神奇的源頭。這樣說吧,Lisp 就像一個拱頂。究竟哪一塊楔形石頭(拱石)托起了整個拱呢?這個問題本身就是錯誤的;每一塊都是。和拱一樣,Lisp 是一組相互契合的特性的集合。 你也可以使用 Common Lisp 的series 宏把代碼寫得更簡潔,但那也只能證明同樣的觀點,因為這些宏就是 Lisp 本身的擴展。 我們可以列出這些特性中的一部分:動態存儲分配和垃圾收集、運行時類型系統、函數對象、生成列表的內置解析器、一個接受列表形式的程序的編譯器、交互式環境等等,但 Lisp 的威力不能單單歸功于它們中的任何一個。是上述這些特性一同造就了 Lisp 編程現在的模樣。 在過去的二十年間,人們的編程方式發生了變化。其中許多變化 交互式環境、動態鏈接,甚至面向對象的程序設計,就是一次又一次的嘗試,它們把 Lisp 的一些靈活性帶給其它編程語言。關于拱頂的那個比喻說明了這些嘗試是怎樣的成功。 眾所周知,Lisp 和 Fortran 是目前仍在使用中的兩門最古老的編程語言。可能更有意思的是,它們在語言設計的哲學上代表了截然相反的兩個極端。Fortran 被發明出來以替代匯編語言。Lisp 被發明出來表述算法。如此截然不同的意圖產生了迥異的兩門語言,Fortran 使編譯器作者的生活更輕松;而 Lisp 則讓程序員的生活更舒服。自從那時起,大多數編程語言都落在了兩極之間。Fortran 和 Lisp 它們自己也逐漸在向中間地帶靠攏。Fortran 現在看起來更像Algol 了,而 Lisp 也改掉了它年幼時一些很低效的語言習慣。 最初的 Fortran 和 Lisp 在某種程度上定義了一個戰場。戰場的一邊的口號是"效率!(并且,還有幾乎不可能實現。)" 在戰場的另一邊,口號是"抽象!(并且不管怎么說,這不是產品級軟件。)" 就好像諸神在冥冥之中決定古希臘戰爭的勝敗那樣,編程語言這場戰爭的結局取決于硬件。每一年都在往 Lisp 更有利的方向發展。現在對 Lisp 的爭議聽起來已經有點兒像1970 年代早期匯編語言程序員對于高級語言的論點。 問題不再是為什么用 Lisp?而是何時用 Lisp?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看