2018年4月27日 星期五

西方憑什麼

每天上下班搭公車通勤,都會看書打發時間,滑手機的話字太小,聽音樂的話公車太吵,發現看書還是最理想的。剛好公司又有借書制度,能看的書多不少,最近看完的是這本<西方憑什麼>,來寫篇心得感想。

<西方憑什麼>想要回答的是一個存在百年的謎題,1840年的甲午戰爭,為何是英國打贏中國,是西方打敗東方,而不是中國艦隊來到泰晤士河大敗英軍?這個問題看似簡單,因為英國發動了工業革命,但若接續問:為何是英國而非中國發動工業革命,可就沒那麼容易回答了。
一般面對這個問題有兩種答案:古早決定論和一時碰巧論。
前者就如<細菌、槍砲與鋼鐵>一書的論點:因為西方條件硬是比較優良,有更多大型動物提供獸力,有更好的氣候,更好的地形,因此發展較為迅速;或者有些論點覺得西方人比較聰明,注定就是西方要發動工業革命;一時碰巧論則認為西方只是一路運氣好,東西方同樣有機會發現新大陸,只是西方運氣好;東西方都能發動工業革命,只是西方運氣好。

作者的立論則認為兩種都不對(當然,不然他寫書幹嘛),作者在前兩章,先回答一個問題:何謂超前,先回答何謂領先,才能評斷領先的原因,否則問西方何以超越東方只是在打高空。
由於過去的狀況都需要用考古跡證來反推,時間會洗掉一切細節,放大反推的困難,又如發掘到文化遺跡,又要如何從文化去分辨優劣?要來辯論拼音字比方塊字還要先進嗎?這種東西根本沒標準答案。
作者選擇了四個指標,都是足夠反映社會強度、容易量化比較、從考古、歷史文件容易推算的項目,包括:地區最大城市人口數、社會平均取用能源、最大軍事能力、資訊處理能力。四項指標多少有點相關,要取用足夠的能源(包含從食物攝取能量)才有多餘的能量分給城市人,有足夠的人口才有足夠的軍事力,足夠資訊處理能力才能撐起大城市。
作者將這四個指數套到東西方文明上,畫出東西兩文明的量化社會發展曲線,這和我們用歷史常識畫出來的曲線驅勢差不多:一如東方從遠古一路上升到漢,五胡十六國時期下跌,唐宋升高,滿清帝國又再次上升,直到近代工業革命後呈指數上升。
作者由兩條曲線,驗證古早決定論確實有其根據,因為西方從美索不達米亞開始,無論是聚落的形成,農業的發展,城邦與國家的興起造成的分數上升,都領先東方 2000 年以上;但也有足夠的證據反駁古早決定論,例如唐宋時期東方一路超過西方,西方要等到東方滿清帝國時代,工業革命前夕才再次超車。

作者論點也很常識:大體來說社會發展伴隨著人口上升,引發資源短缺與社會動盪,各個國家都有發展天花板,如漢朝、羅馬,都無法突破發展天花板而面臨下跌,但若抓住時機突破天花板,無論是取用全新的資源,制度上的變革,突破之後就能再次引領上升,例如晉將長江一帶開發為沃土,成為唐宋盛世的基礎;工業革命解放化石能源,其成果自不待言。
作者多少也是抱持古早決定論的,西方之所以能搶先發現美洲,建立大西洋經濟圈,純粹就是大西洋比太平洋還要窄,西歐又有繞過威尼斯、土耳其到印度的動機,如果給東方多個兩百年,也許也能建成太平洋經濟圈,發展工業革命,當然歷史不會有如果,搶先解放化石能源的西方自此統領世界到今天(作者持悲觀論,因為沒有動機,就算沒有西方打擾東方也要許久才會嘗試橫渡太平洋)。

我認為作者在本書最大的貢獻,在於提供一個足夠客觀的量化證據,讓古早決定論或一時湊巧論,都能在這個量化證據上進行辯駁。內容反而不那麼重要,剩下幾章作者都在說故事,將他的論點代入歷史證劇,還原當時的社會概況,可以當成小說一樣讀過去。
最後一章作者試圖預測未來世界的走向,以及西方是否能持續領先,但呈現出來的效果比較像「我大膽預測,明天的股票不是漲就是跌」過去各文明都曾在不同的地方遇上瓶頸,誰能預料現代社會不會在 1000 分遇到另一個上界?從分數可以看出古文明遇上什麼困難,卻無法預知未來,飢荒、天災、戰爭、疾病一直是歷史上的常客,受惠於科技進步,這些問題看似都能解決(也許因為人類的愚蠢,戰爭例外),但又有誰能預測下一場巨大災害?人類歷史大多都是矇眼爬山,在無限的未知中尋求道路,然後祈禱老天保佑,現在也差不了多少。

看完本書籍是有些感想,首先如作者所言,英雄和天才並不存在,社會的發展基於社會集體,找個社會隨機抽 100 人出來,各類型的人的比例都差不多。工業革命之所以在英國,並不是瓦特有三頭六臂,而是英國社會正好在煤礦坑排水問題上,需要比獸力更高階的能源,鐵器製造和生產也正好跟上蒸氣機所需;前導的科學革命也是因為當時跨大西洋經濟體成形,需要新的知識來解決全新的問題,例如在海上測定經度,需要以機械化、科學化的方式去觀察自然的運作。
歷史就是一連串的見神殺神、見佛殺佛;何以秦朝和羅馬幾乎在同時出現君主集權的國家?為何春秋和希臘的哲人們,會同時發展出眾多不同的治國思想和哲學?明鄭和西歐會同時向海洋前進?為何阿拉伯會在天文學上保持領先直到 16 世紀?
愛因斯坦不會比亞里斯多德聰明,張衡也不會比沈括差太多,社會遇到什麼問題,大家就努力突破,僅此而已。
所以說,問對問題,創造解決問題的環境,比生出一個兩個天才更重要;隨著社會更複雜,未來更重要的是組織和打群架,不要期待天才解決問題,一個人甚至一百人都不是關鍵,組織和環境才是;同時不要害怕嘗試,舊方法解決不了新問題,嘗試新方法、新制度才有突破的可能,曾經我們以為採集勝過農業、以為分封勝過帝制、以為女性應該關在家裡不事生產,如今都在實驗之後被證明優劣,裹足不試的國家無法引領未來。

另外,我曾經聽過許多都市傳說,主張人類文明的跳躍,都是由外星人帶來,像是金字塔、積體電路、IBM 5100 等等,實際上歷史和文明都是一層層的累積,就如同考古的沉積層,從底層的石器到淺層的鐵渣,只不過越接近現代,累積愈為迅速。
重大發明看似跳躍,但我們仍能從小型、中型、大型金字塔;從一開始的鍺電晶體、矽電晶體到積體電路,看到背後的進展歷程和解決思維,瓦特也不是發明蒸汽機,而是改良無效率的蒸汽機(Miner’s friend),所謂外星人的巨大黑石板,純屬無稽之談。

這裡也能延伸兩個想法,第一個是所謂的<全新工作>,農場文常說未來十年會有多少工作被消滅,認為現在人應該為明日的工作做好準備,但現在我覺得:所有的全新工作,其實都是基於現有工作難題而來,資料科學是要處理從雲端服務收集到的大量資料,積體電路是為了解決大量電晶體生產除錯困難的問題;沒有所謂的為未來工作做準備這回事,預測未來工作最好的方式就是把舊有工作做到最好,解決痛點時就成為新的工作了,連現有工作的痛點在哪都不知道,以為新工作會突然噴出來,無異守株待兔。

第二就是所謂的彎道超車,最近中興晶片事件鬧得很大,正顯出工業革命以降,歐美累積出的知識、技術和資本是如何的深厚,近二三十年中國的快速成長和歐美的金融風暴,再再都予人西方已日薄西山之感,但現實正好相反,人們(有時包括西方自己)都很難意識到西方手握的資源是何等龐大,即便西方成長率不如東方各國,乘在巨大的基礎上,還是能不斷保持領先。
翻翻中興相關的文章就能看到,美國的科技大廠一年砸了多少錢去做研發,藉此保持在技術上的領先,技術上面又有連帶的生態系,光一條鏈就把你壓的死死的,超車何等困難。
當然這樣還是太悲觀了,其實現代社會產業鏈之複雜,在林林總總的產業總有不同的問題等待解決,只要能在一個領域把事情做好,就有領先的可能性,好比如台積電把做晶圓製造這件事做到最好,假設我們先不想要在整個科技領域上全面性的超車,只要加入世界產業鏈裡面,到處都有小螺絲的位子(好吧也許這對某些國家來說有些困難)。

如果真的想要在領域當霸主,也不是這樣喊打喊殺就能成事,而是耐著性子去重新打造輪子,重打輪子不是隨便打,他們一定是看到需要更好的地方:ARM 提供嵌入式系統更省電跟精簡的設計;LLVM 提供更模組、更有秩序的編譯環境;Mozilla Servo 大量使用 06 年後 CPU 平行化的功能,加速網頁瀏覽;總是有那些小小的利基點,能夠做出一點點的差異,讓後繼者在某些領域茁壯,進而挑戰現有霸主的地位。
當然可以聚焦在現有系統的微幅改進,不敢去想下一代的東西,只是這樣註定做不出 ground breaking 的東西;等到 ground breaking 的東西出來了,驚訝之餘也就只能徒呼負負了。
重新打造輪子必定是個死傷泰半……不!是個<只有一位能活下來>的行為,但即便是最後沒人用的輪子,也會在打造的過程中習得<如何打造輪子>的技能,也許某一天這項技巧就能用來打造出真正能用的輪子;真的就是那句「豔陽高照,練兵完畢」,連兵都不想練,哪能期待看到豔陽?

本來想打個簡單的心得,結果不小心打了超長一篇……,混合了書本介紹、心得和一些過去的發文。
這本書以知識密度上來說有點不足,其實沒有很推,由於我們本身熟於東亞歷史,全書一半的內容只是稍稍重複課本內容,如果真的有閒的話,拿來當故事書看看,溫習一下中西歷史也不錯就是了。

總評:5/10
簡單評論:浪費時間 看看就好* 值得一看 非看不可

2018年4月6日 星期五

整理 rust module 的安排方式

故事是這樣子的,兩年前因為傳說中的 jserv 大神的推薦,我讀了 Understanding Computation 這本書,讀完覺得學到很多東西,深受啟發;後來大概花了兩個月的時間,用Rust 重寫了裡面所有的範例程式碼,目前在 github 上查了一下,我應該是除了原作實作之外,實作最完整的一個,可謂一人之下,萬人之上(誤。
最近因為一些原因,把之前的實作打開來看,馬上關上,假的!趕快在筆電前面打坐。
當初到底怎麼寫這麼醜,還查到有些章節的內容沒有實作完,那時候可能太難不會寫,先跳過結果就忘了QQ……最近這一兩個禮拜陸續花了一點時間整理。

這次整理的一大修正,是把本來是散在各處的原始碼,重新照 rust 慣例統整到 src 資料夾下面,並使用 cargo 管理,帶來的好處包括有:可以一次 cargo build 編譯所有程式;引入 cargo test 代替本來編譯成執行檔用 println debug 的實作;在各章的內容間重用 module ,提升重用比例;另外也能使用網路上其他人寫的 Rust module(其實這才是原初整理的目的)。
例如在我之前實作的程式碼,在寫 finite automata 時,dfa, nfa 各自有一個實作,使用 u32 作為狀態;但到了 regular expression 的時候,為了產生 nfa 就不能用 u32 作為狀態,於是我複製了一版 nfa,改成用 object pointer 作為狀態,兩者程式碼的重複率就非常高,這次也一併改成 generic 的 nfa 實作,兩邊就能分享同一套程式碼。

本來以為整理就是把程式碼搬一搬,也差不多就結束了,結果不是(當然有一部分是我自己蠢),但也是因為這個機會,弄懂的 Rust 的 module 系統,這裡作個記錄。

在一個 rust project 當中,最重要的都放在 src 資料夾下面,包括要編成函式庫或執行檔的原始碼都在這裡,其他像編譯結果的 target、文件的 doc、測試的 test ,相對來說比較沒這麼重要(好啦還是很重要,只是不是本文要討論的重點)。

rust 的編譯是由 cargo.toml 所驅動,一個 rust 模組只能編出一個 rlib,由 cargo.toml 的 [lib] 所指定,例如我這個專案指定 library 名稱跟路徑如下:
[lib]
name = "computationbook"
path = "src/lib.rs"
如果不設定的話,cargo 會預設編譯 src/lib.rs,並自動從資料夾名稱產生 library 名稱;這個 library 名稱非常重要,先把它記著。

函式庫的實作就在 lib.rs 裡面,包含函式庫所有的函式,可以加上 pub 讓外界可以取用;當函式多的時候,就開始需要幫他們分門別類,也就是 rust 的 module:
fn libraryfun () {}
mod modulename {
 fn libraryfun () {}
}
用起來有點像 C++ 的 namespace,上面例子中,兩個 libraryfun 是不會衝突,一個是 libraryfun,一個是 modulename::libraryfun。
然後 Rust 以它預設什麼都不開放的嚴謹著稱,上面的不寫 pub mod,pub fn 的話,外面是無法取用的。

函式更多,可以把整個 module 移到另一個檔案,lib.rs 裡面只留下 mod 宣告:
mod modulename;
內容移到 modulename.rs 裡面;或者有足以獨立出來的功能,可以放到 modulename 資料夾下,並加上 modulenmae/mod.rs 來表示這個資料夾是一個 module。

module 大致的規則就是這樣
一:可直接在檔案內定義。
二:只寫 mod modulename,內容放在其他檔案。
三、只寫 mod modulename,內容放在同名且內含 mod.rs 的資料夾內。
注意第二、三個方法互相衝突的,不能有 mymodule.rs 跟 mymodule/mod.rs 同時存在,rust 會跳出編譯錯誤。
另外有一個特例是在 src 下,只有 lib.rs 有權限宣告 submodule,例如 lib.rs 宣告一個叫 network 的 module 並把內容放在 src/network.rs,network.rs 就不能再宣告它有一個 module server。
// src/lib.rs
mod network;
// src/network.rs
mod server; // compile error
這裡的 error 訊息很詭異,是 file not found for module ‘server’,但初遇時覺得見鬼,檔案就在那邊你跟我說沒有…。
正確作法是把 network.rs 移到 network 資料夾中,改名為 mod.rs 指明這個資料夾下有哪些 module,這時候 src/lib.rs 裡面的 mod network; 就會指向 network/mod.rs 裡寫的 mod;把 server.rs 放在network 資料夾中,就適用上述的規則二,在 mod.rs 裡只寫 mod server;,內容放在其他檔案。

講完 module 的定義,現在可以來講引用,這部分要分成兩種,一是 src 下函式庫的引用:
上面第三種規則的,拿我的 dfa module 為例, mod.rs 定義了 dfarulebook.rs 跟 dfa.rs ,dfa 需要 dfarulebook,因為他們都在 dfa module 內,要引用時就用:
use super::dfarulebook::{DFARulebook};
因為這些檔案不太可能會分開,用 super 的好處是無論 dfa 資料夾到哪這個參照都不會變;在 mod.rs 裡面實作測試的 module 也是一樣,測試當然需要原本 module 裡的東西,這個時候也是使用 super:
mod dfarulebook;
#[cfg(test)]
mod tests {
  use super::dfarulebook::{DFARulebook};
同樣是第三種規則的 mod.rs,mod.rs 本身就是 dfa module,它需要用到 dfarulebook.rs 的內容,則是:
mod dfarulebook;
mod dfa;
use self::dfarulebook::{DFARulebook};

來自 dfa 之外的引用就要用完整路徑,從 src 開始一路指定:
use the_simplest_computer::dfa::dfarulebook::{DFARulebook};
你可以想像,從 src/lib.rs 開始,要可以透過一連串的 mod.rs 找到我們要的 module;上面的 lib.rs 裡就有 mod the_simplest_computer; ;the_simplest_computer/mod.rs 裡有 mod dfa; 一路像串棕子一樣把各模組串起來,如果有地方沒串好,cargo 就會回報錯誤。

再來是執行檔,這裡我也是試好久才試出來。
現在的 cargo 可以在 Cargo.toml 裡面,用 [[bin]] 欄位指定多個執行檔目標,不然就是預設的 src/main.rs,這跟 library 不同,一個 Cargo.toml 就只能有一個 [lib] 目標。
這裡關鍵的點就是:執行檔不是 library 的一部分,cargo 先從 lib.rs 編譯出函式庫,然後編譯執行檔跟函式庫做連結;執行檔要用編譯的函式庫,就要先宣告 extern crate,然後每層 use 都在前面多加上函式庫,上面說要記著的函式庫名稱就是這裡要用到:
extern crate computationbook;
use computationbook::the_simplest_computer::dfa::dfarulebook::{DFARulebook};
超級長對吧XD
如果函式庫名稱是 cargo 自動產生,可以去 target/debug/ 看它編譯出什麼,我上面的例子就是:libcomputationbook.rlib

故事大概就到這裡,這次總算弄懂 cargo 如何管理各 module 了,感謝大家收看。

在這裡就雷一下大家,下一篇就要來說一下,本來整理這個 project 要做的東西,估計會是一篇理論跟實作兼具的文章,敬請期待。

本文之完成,感謝以下參考資料:
Cargo guide: https://doc.rust-lang.org/cargo/guide/
Rust module guide: https://doc.rust-lang.org/book/second-edition/ch07-01-mod-and-the-filesystem.html
minisat-rust 專案,因為它編得過我編不過,它的複雜度又剛剛好夠複雜,就拿來研究了一番:https://github.com/mishun/minisat-rust