莫看,莫看,莫看。重要的事說三遍,浪費時間我無論。
文化編程(Literate Programming)由計算機學界名宿 Donald Knuth 所創。其名,大陸譯爲「文學編程」,甚繆。將程序視爲蠻夷,以文教化,Literate Programming 之意大抵如是。git
文化,其目的在於實現人與人的溝通交流。假若這世上只有一人,文化是多餘的。同理,假若世上只有一個編程者,文化編程也是多餘的。程序的屬性分爲兩部分,一是供機器執行,二是供人類閱讀。程序能正確被機器執行,這是第一性,但程序要繼續維護下去,其代碼更易於爲他人理解,這是第二性,兩者缺一不可。因此,程序要有文化。github
Knuth 創立文化編程,江湖傳言,是報 E. W. Dijkstra 的一箭之仇。 Dijkstra 也是計算機學界名宿。當年,他提出告終構化編程,指出應當像設計機器那樣編寫程序,而 Knuth 寫的程序不夠結構化,或許爲此而不快。創立文化編程以後,Knuth 就能夠說 Dijkstra 沒文化,固然他不會這樣說。編程
江湖恩仇,兒女情長,這些事,提及來沒完沒了,到此爲止。這篇文章,真正的主角是我。segmentfault
Knuth 雖然提出了文化編程,惋惜的是,他創造的文化編程工具 CWEB 有歷史侷限性,如今不是很好用。這能夠理解,畢竟這個工具最初是他自用,而且在寫這個工具之時,也只有美國人有更多的機會使用計算機。工具
好在這種工具實現起來也不太難,去年過年的那段時間,我寫了一個相似的工具,名曰 orez,意思是 zero 翻轉。爲了防止百年以後有人再亂翻譯,我先將它翻譯爲逆零 :)ui
orez 基本上遵循 CWEB 的思路——程序的代碼與文檔,彼此獨立,但又混而爲一。若以泡茶爲喻,則代碼之於茶葉,文檔之於水,而 orez 之於茶壺。代碼與文檔的混合體,可稱之爲有文化的代碼。翻譯
代碼與文檔的混合須要藉助一組簡單的標記方能實現。下面,以一組五言詩句(姑且當其是)爲代碼,以詩句解析做爲文檔,演示這些標記的用法。這組詩句很直白,是一位無名氏,於一次磨刀以後隨手而做,記錄了磨刀的過程,讚美了磨過以後刀如何鋒利,最後感慨真正鋒利的刀無人賞識而隱沒於陋鞘。設計
代碼與文檔皆以段落的形式出現,而且老是以 @
符號領起。例如:code
@ 一位俠士用水浸泡砥石,準備親手去磨已鈍了的未名刀: @ 準備 # 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名
兩者的區別是,代碼段落有名字,即 @
與 #
之間的文字,例如「準備
」。代碼段落的名字可根據實際狀況而取,此處僅僅是追求足夠簡單。文檔
假若有文化的代碼是以文檔開始,那麼首個文檔段落的領起符號可省略。例如:
俠士用水浸泡砥石,準備親手去磨已鈍了的未名刀: @ 準備 # 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名
若假設代碼段落以後總存在一個文檔段落,哪怕這個段落沒有任何內容,那麼文檔段落的領起符即可以在形式上做爲代碼段落的終止符。雖然沒必要非如此不可,可是在以文檔段落開始的狀況下,我更喜歡這種形式。例如:
俠士用水浸泡砥石,準備親手去磨已鈍了的未名刀: @ 準備 # 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名 @
這樣一來,若代碼段落後面是文檔段落,那麼該文檔段落領起符已存在。若代碼段落後面依然是代碼段落,那麼兩者之間會夾雜一個內容爲空的文檔段落,無礙。
接下來再繼續增長文檔段落與代碼段落:
磨刀,主要是靠腰部力量,手起到固定刀片傾斜角度的做用: @ 姿式 # 力從足下起 腰旋若扭鬆 雙手互犄角 刃行掠風輕 @ 刀片在砥石上的前進方向老是與鋒線垂直,而且須要時不時用手試 一下刀鋒是否已磨到卷邊的程度: @ 技巧 # 鋒線有曲直 法向自無知 進退兩茫然 探手試微疵 @
假設全部的文檔段落與代碼段落皆已寫出,那麼最後可經過代碼段落的引用標記,將全部已給出的代碼段落彙總到一塊兒。例如:
@ 磨刀歌 # # 準備 @ # 姿式 @ # 技巧 @ ... ... @
形如 # ... @
這樣的結構,即是已給出的代碼段落的引用,亦即所引用的代碼段落的內容最終會嵌入到被引用處。同一個代碼段落,可於多處被引用。
假若不想爲每一個代碼段落都取個名字,畢竟取名字很煩人,能夠考慮使用同名代碼段,運算符 +
用於標記同名代碼段落內容鏈接起來,表示一個完整的代碼段落。
例如:
俠士用水浸泡砥石,準備親手去磨已鈍了的未名刀: @ 磨刀歌 # 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名 @ 磨刀,主要是靠腰部力量,手起到固定刀片傾斜角度的做用: @ 磨刀歌 # + 力從足下起 腰旋若扭鬆 雙手互犄角 刃行掠風輕 @ 刀片在砥石上的前進方向老是與鋒線垂直,而且須要時不時用手試 一下刀鋒是否已磨到卷邊的程度: @ 磨刀歌 # + 鋒線有曲直 法向自無知 進退兩茫然 探手試微疵 @
上述三個代碼段落,雖然分散各處,但它們其實是由兩個 +
運算符鏈接而成的一個總體:
@ 磨刀歌 # 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名 力從足下起 腰旋若扭鬆 雙手互犄角 刃行掠風輕 鋒線有曲直 法向自無知 進退兩茫然 探手試微疵 @
+
運算符表示自上而下順次鏈接同名的代碼段落,其逆運算符 ^+
則表示自下而上鍊接同名的代碼段落。例如:
@ 磨刀歌 # ^+ 磨刀歌 無名氏 @
這時,完整的代碼段落以下:
@ 磨刀歌 # 磨刀歌 無名氏 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名 力從足下起 腰旋若扭鬆 雙手互犄角 刃行掠風輕 鋒線有曲直 法向自無知 進退兩茫然 探手試微疵 @
有時,須要將一個代碼段落鏈接到某個特定的同名代碼段落以前或以後,這時須要藉助標籤。
首先須要在目標段落上放一個標籤。例如:
@ 磨刀歌 # <第一節> 滄浪三瓢水 砥石浴盆中 俠士自磨刀 霜刃名未名 @
<第一節>
即標籤。標籤必須置於代碼段落標題行的下一行,且獨佔一行。
在源段落中,將目標段落標籤與 +
或 ^+
運算符組合起來,即可將源段落與目標段落鏈接起來。例如:
@ 磨刀歌 # <第一節> ^+ 磨刀歌 無名氏 @
結果依然是:
@ 磨刀歌 # 磨刀歌 無名氏 滄浪三瓢水 砥石浴盆中 ... ... @
假設程序代碼與文檔內容混合體——有文化的代碼,存於文本文件 foo.orz。如今,若從中導出給人看的文檔——可喻爲倒茶喝,只需使用如下命令:
$ orez -w foo.orz -o foo.yml
生成的 foo.yml 是一份帶有排版信息的中間文件,它最終能轉化爲何文件,這取決於程序代碼與文檔內容是基於何種排版語言混而爲一。假若是基於 Markdown 語言,即可使用 orez-md 工具,將 foo.yml 轉換爲 foo.md 文件(Markdown 文件),而後,再用其餘工具將 foo.md 文件轉化爲網頁或其餘形式的文檔形式。
在 Linux 系統(或類 Unix 系統)中,從 foo.orz 到 foo.md 的整個過程,可濃縮爲一條命令:
$ orez -w foo.orz | orez-md > foo.md
foo.md 就是從 orez 這個茶壺嘴裏流出的茶水。
一壺茶喝到了沒滋味的時候,又該怎樣倒出茶葉呢?打開壺蓋,倒出來。不過,爲了更貼近 orez 的邏輯,須要將茶葉視爲袋泡茶,揪住袋上棉線,將茶葉從茶壺中拎出來。例如:
$ orez -t -e "磨刀歌" foo.orz -o mdg.txt
最後所得 mdg.txt 文件即是以 磨刀歌
爲棉線而拎出的袋泡茶。
更多細節見 《orez 的故事》。之因此又重寫一篇簡略介紹,是由於之後要用 orez 的一些標記。