這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文 Less is exponentially more 是 Rob Pike 自己整理的他在六月22日,舊金山的 Golang 會議上的演講稿。清晰的介紹了 Go 的前世今生,來龍去脈。為了讓更多的人能夠更加清楚的認識到 Go 的優雅並喜愛上 Go,特翻譯成中文,以飧讀者。
—————-翻譯分隔線—————-
大道至簡
這是我(Rob Pike)在 2012 年六月,舊金山 Go 會議上的演講內容。
這是一個私人演講。我並未代表 Go 項目團隊的任何人在此演講,但我首先要感謝團隊為 Go 的誕生和發展所做的一切。同時,我也要感謝舊金山 Go 社區給我這個演講機會。
在幾個星期之前我被問到,“在推出 Go 之後,什麼令你感到最為驚奇?”我立刻有了一個答案:儘管我們希望 C++ 程式員來瞭解 Go 並作為一個可選的語言,但是更多的 Go 程式員來自如於 Python、Ruby。只有很少來自 C++。
我們——Ken,Robert 和我自己曾經是 C++ 程式員,我們設計新的語言是為瞭解決那些我們編寫的軟體中遇到的問題。而這些問題,其他 C++ 程式員似乎並不怎麼在意,這看起來有些矛盾。
今天我想要談談是什麼促使我們建立了 Go,以及為什麼本不應該是這樣會我們驚訝的結果。我承諾討論 Go 會比討論 C++ 多,即便你不瞭解 C++ 也仍然完全跟得上主題。
答案可以概括為:你認為少既是多,還是少就是少?
這裡有一個真實的故事作為隱喻。貝爾實驗室最初用三個數位識別碼:111 表示物理研究,127 表示電腦科學研究,等等。在上世紀八十年代早期,一篇如期而至的備忘錄聲明由於我們所瞭解的研究正在增長,為了便於識別我們的工作,必須再添加一位元。因此,我們的中心變為 1127。Ron Hardin 開玩笑半認真的說道,如果我們真的更好的瞭解了這個世界,我們可以減少一位元,使得 127 僅為 27。當然管理層沒有聽到這個笑話,又或者他們不願意聽到,但是我想這其中確有大的智慧。少既是多。你理解得越好,越含蓄。
請務必記住這個思路。
回到 2007 年 9 月,我在一個巨大的 Google C++ 程式(就是你們都用過的那個)上做一些瑣碎但是很核心的工作,我在那個巨大的分布式叢集上需要花大約 45 分鐘進行編譯。收到一個通知說 Google 僱傭的一對為 C++ 標準化委員會工作的夫婦將會做一場報告。收到一個通知說幾個受雇於 Google 的為 C++ 標準化委員會工作的人將會做一場報告。他們將向我們介紹那時還被稱作 C++0x(就是現在眾所周知的 C++11)中將會有哪些改進。
在長達一個小時的報告中,我們聽說了諸如有已經在計劃中的 35 個特性之類的事情。事實上有更多,但僅有 35 個特性在報告中進行了描述。當然一些特性很小,但是意義重大,值得在報告中提出。一些非常微妙和難以理解,如左右值引用(rvalue references),還有一些是 C++ 特有的,如可變參數模板(variadic templates),還有一些就是發瘋,如使用者定義資料標識(user-defined literals)。
這時我問了自己一個問題:C++ 委員會真得相信 C++ 的問題在於沒有足夠的特性?肯定的說,在另一個 Ron Hardin 的玩笑中,簡化語言的成就遠遠大於添加功能。當然這有點可笑,不過請務必記住這個思路。
就在這個 C++ 報告會的數月前,我自己也進行了一場演講,你可以在 YouTube 上看到,關於我在上世紀 80 年代開發的一個玩具性質的並發語言。這個語言被叫做 Newsqueak,它是 Go 的前輩了。
我進行這次報告是因為在 Newsqueak 中缺失的一些想法,在為 Google 工作的時候我再次思考了這些它們。我當時確信它們可以使得編寫服務端代碼變得更加輕鬆,使得 Google 能從中獲得收益。
事實上我曾嘗試在 C++ 中實現這些思路,但是失敗了。要將 C++ 控制結構和並行作業聯絡起來太困難了,最終這導致很難看到真正的優勢。雖然我承認我從未真正熟練的使用 C++,但是純粹的 C++ 仍然讓所有事情看起來過於笨重。所以我放棄了這個想法。
但是那場 C++0x 報告讓我再次思考這個問題。有一件令我十分困擾的事情(同時我相信也在困擾著 Ken 和 Robert)是新的 C++ 記憶體模型有原子類型。感覺上在一個已經負擔過重的類型系統上加入如此微觀的描述細節的集合是絕對的錯誤。這同樣是目光短淺的,幾乎能確信硬體在接下來的十年中將迅速發展,將語言和當今的硬體結合的過於緊密是非常愚蠢的。
在報告後我們回到了辦公室。我啟動了另一個編譯,將椅子轉向 Robert,然後開始溝通關鍵的問題。在編譯結束前,我們已經把 Ken 拉了進來,並且決定做些什麼。我們不準備繼續寫 C++ 了,並且我們——尤其是我,希望在寫 Google 代碼的時候能夠做輕鬆的編寫並發。同時我們也想勇往直前的駕馭“大編程”,後面會談到。
我們在白板上寫了一堆想要的東西,和其必要條件。忽略了文法和語義細節,設想了藍圖和全域。
我這裡還有那時的一個令人神混魂顛倒的郵件。這裡摘錄了一部分:
Robert: 起點:C,修複一些明顯的缺陷,移除雜物,添加一些缺失的特性。
Rob: 命名:“go”。你們可以編造這個名字的來由,不過它有很好的底子。它很短,容易拼字。工具:goc, gol, goa。如果有互動式調試器/解譯器,可以就叫做“go”。副檔名是 .go。
Robert 空介面:interface {}。它們實現了所有的介面,所以這個可以用來代替 void *。
我們並沒有正確描繪全部的東西。例如,描繪 array 和 slice 用了差不多一年的時間。但是這個語言特色的大多數重要的東西都在開始的幾天裡確定下來。
注意 Robert 說 C 是起點,而不是 C++。我不確定,不過我相信他是指 C,尤其是 Ken 在的情況下。不過事實是,最終我們沒有從 C 作為起點。我們從頭開始,僅僅借鑒了如運算子、括弧、大括弧、和部分關鍵字。(當然也從我們知道的其他語言中吸取了精髓。) 無論如何,我們現在同 C++ 做著相反的事情,解構全部,回到原點重新開始。我們並未嘗試去設計一個更好的 C++,甚至更好的 C。僅僅是一個對於我們在意的那種類型的軟體來說更好的語言。
最終,它成為了一個與 C 和 C++ 完全不同的語言。每個發布版本都越來越不同。我製作了一個 Go 中對 C 和 C++ 進行的重要簡化的清單:
- 規範的文法(無需用於解析的符號表)
- 垃圾收集(唯一)
- 沒有標頭檔
- 明確依賴
- 無循環相依性
- 常量只能為數字
- int 和 int32 是不同的類型
- 字母大小寫設定可見度
- 任何類型都可以有方法(沒有類)
- 沒有子類型繼承(沒有子類)
- 包層級初始化和定義好的初始化順序
- 檔案編譯到一個包中
- 包層級的全域表達與順序無關
- 沒有算術轉換(常量做了輔助處理)
- 隱式的介面實現(無需“implements”定義)
- 嵌入(沒有向父類的升級)
- 方法如同函數一樣進行定義(沒有特的別位置要求)
- 方法就是函數
- 介面僅僅包含方法(沒有資料)
- 方法僅通過名字匹配(而不是通過類型)
- 沒有構造或者析構方法
- 後自增和後自減是語句,而不是運算式
- 沒有前自增或前自減
- 賦值不是運算式
- 按照賦值、函數調用定義時的順序執行(沒有“sequence point”)
- 沒有指標運算
- 記憶體總是零值初始化
- 對局部變數取地址合法
- 方法沒有“this”
- 分段的堆棧
- 沒有靜態或其他類型註解
- 沒有模板
- 沒有異常
- 內建 string、slice、map
- 數組邊界檢查
除了這個簡化清單和一些未提及的瑣碎內容,我相信,Go 相比 C 或者 C++ 是更加有表達力的。少既是多。
但是即便這樣也不能丟掉所有東西。仍然需要構建類型工作的方式,在實踐中恰當的文法,以及讓庫的互動更好這種令人感到忌諱不可言喻的事情。
我們也添加了一些 C 或者 C++ 沒有的東西,例如 slice 和 map,複合聲明,每個檔案的頂級運算式(一個差點被忘記的重要東西),反射,垃圾收集,等等。當然,還有並發。
當然明顯缺少的是類型層次化。請允許我對此爆那麼幾句粗口。
在 Go 最初的版本中,有人告訴我他無法想像用一個沒有範型的語言來工作。就像之前在某些地方提到過的,我認為這絕對是神奇的評論。
公平的說,他可能正在用其自己的方式來表達非常喜歡 STL 在 C++ 中為他做的事情。在辯論的前提下,讓我們先相信他的觀點。
他說編寫像 int 列表或 map string 這樣的容器是一個無法忍受的負擔。我覺得這是個神奇的觀點。即便是那些沒有範型的語言,我也只會花費很少的時間在這些問題上。
但是更重要的是,他說類型是放下這些負擔的解決途徑。類型。不是函數多態,不是語言基礎,或者其他協助,僅僅用類型。
這就是卡住我的細節問題。
從 C++ 和 Java 轉過來 Go 的程式員懷念工作在類型上的編程方式,尤其是繼承和子類,以及所有相關的內容。可能對於類型來說,我是門外漢,不過我真得從未發現這個模型十分具有表達力。
我已故的朋友 Alain Fournier 有一次告訴我說他認為學術的最低級形式就是分類。那麼你知道嗎?類型層次化就是分類。你必須對哪塊進哪個盒子作出決策,包括每個類型的父級,不論是 A 繼承自 B,還是 B 繼承自 A。一個可排序的數組是一個排序過的數組還是一個數組表達的排序器?如果你堅信所有問題都是由類型驅動設計的,那麼你就必須作出決策。
我相信這樣思考編程是荒謬可笑的。核心不是東西之間的祖宗關係,而是它們可以為你做什麼。
當然,這就是介面進入 Go 的地方。但是它們已經是藍圖的一部分,那是真正的 Go 哲學。
如果說 C++ 和 Java 是關於類型繼承和類型分類的,Go 就是關於組合的。
Unix pipe 的最終發明人 Doug McIlroy 在 1964 (!) 這樣寫到:
我們應當像串連花園裡的龍頭和軟管一樣,用某種方式一段一段的將訊息資料連線起來。這同樣是 IO 使用的辦法。
這也是 Go 使用的辦法。Go 用了這個主意,並且將其向前推進了一大步。這是一個關於組合與串連的語言。
一個顯而易見的例子就是介面為我們提供的組合元件的方式。只要它實現了方法 M,就可以放在合適的地方,而不關心它到底是什麼東西。
另一個重要的例子是並發如何串連獨立啟動並執行計算。
並且也有一個不同尋常(卻非常簡單)的類型組合模式:嵌入。
這就是 Go 特有的組合技術,滋味與 C++ 或 Java 程式完全不同。
===========
有一個與此無關的 Go 設計我想要提一下:Go 被設計用於協助編寫大程式,由大團隊編寫和維護。
有一個觀點叫做“大編程”,不知怎麼回事 C++ 和 Java 主宰了這個領域。我相信這隻是一個曆史的失誤,或者是一個工業化的事故。但是一個廣泛被接受的信念是物件導向的設計可以做些事情。
我完全不相信那個。大軟體確實需要方法論保駕護航,但是用不著如此強的依賴管理和如此清晰的介面抽象,甚至如此華麗的文檔工具,但它不比強大的依賴管理、清晰的介面抽象和優秀的文檔工具來得更重要,而這些沒有一樣是 C++ 做好的事情(儘管 Java 明顯做得更好一些)。
我們還不知道,因為沒有足夠的軟體採用 Go 來編寫,不過我有自信 Go 將在大編程領域脫穎而出。時間證明一切。
===========
現在,回到我演講一開始提到的那個令人驚奇的問題:
為什麼 Go,一個被設計為用於摧毀 C++ 的語言,並為並未獲得 C++ 程式員的芳心?
撇開玩笑不說,我認為那是因為 Go 和 C++ 有著完全不同的哲學。
C++ 是讓你的指尖解決所有的問題。我在 C++11 的 FAQ 上引用了這段內容:
C++ 與那些巨大增長的特別編寫的手工代碼相比,具有更加廣泛的抽象,優雅、靈活並且零成本的表達能力。
這個思考的方向與 Go 的不同。零成本不是目標,至少不是零 CPU 成本。Go 的主張更多考慮的是最小化程式員的工作量。
Go 不是無所不包的。你無法通過內建獲得所有東西。你無法精確控制每個細微的執行。例如沒有 RAII。而可以用垃圾收集作為代替。也沒有記憶體釋放函數。
你得到的是功能強大,但是容易理解的,容易用來構建一些用於串連組合解決問題的模組。這可能最終不像你使用其他語言編寫的解決方案那麼快,那麼精緻,在思想體繫上那麼明確,但它確實會更加容易編寫,容易閱讀,容易理解,容易維護,並且更加安全。
換句話說,當然,有些過於簡單:
Python 和 Ruby 程式員轉到 Go 是因為他們並未放棄太多的表達能力,但是獲得了效能,並且與並發共舞。
C++ 程式員無法轉到 Go 是因為他們經過艱辛的戰鬥才獲得對其語言的精確控制能力,而且也不想放棄任何已經獲得的東西。對於他們,軟體不僅僅是關於讓工作完成,而是關於用一個確定的方式完成。
那麼,問題是,Go 的成功能否反駁他們的世界觀。
我們應當在一開始的時候就意識到了一點。那些為 C++11 的新特性而興奮的人們是不會在意一個沒有這麼多特性的語言。即便最後發現這個語言能夠比他們所想象的提供更多。
謝謝大家。
感謝 Leo Jay、fango、techabc、spin6lock、panovr、lihui、許大、幻の上帝 的訂正。根據 Monnand 的建議,將標題修改為《大道至簡》