Kan-Ru Chen's Weblog

Literate Programming

Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.
― Donald Knuth. "Literate Programming (1984)"

Thinker 的一篇心靈與程式碼的協奏曲提到程式由後往前寫似乎比較符 合思考的方向,不禁讓我想到 Knuth 提出的 Literate Programming 方 法;其實我們思考的順序有時是跳躍式的,用 LP 的方法可以完全跳脫先後關係, 用自己喜歡的順序來寫,我覺得也可以稱為碎碎唸寫法。

用 Thinker 的同一個例子,改作 noweb 的格式,用 LP 的方法來寫,會變 成怎樣?首先定義問題:

假設問題是,function 接受一字串,裡面的每一行有兩個欄位,皆是整數。欄 位以一個空白相隔。function 必把每一行的兩個欄位的數字相乘,然後再將每 一行的結果加總。

我們需要一個接受一個字串的 function :

    <<接受字串的 function>>=
    def mul_n_sum(data):
        <<function 內容>>
    @

這個 function 必須把每一行的兩欄數字相乘,然後相加;假設我們取得每一行 的兩個數字之後照順序存放在 field_lines 這個變數裡面,則用 sum 就可 以把兩兩相乘之後的數字加總:

    <<function 內容>>=
    <<取得每一行的兩個數字>>
    return sum([field1 * field2 for field1, field2 in field_lines])
    @

要怎麼取得每一行的兩個數字呢?首先要把每一行的字串切開:

    <<取得每一行的兩個數字>>=
    lines = data.strip().split('\n')
    @

然後被切開的每一行字串經過分割後,會變成依照

    [field1, field2],
    [field1, field2],
    [field1, field2],
    ...

順序排列的字串,所以我們還要轉換成數字才能運算:

    <<取得每一行的兩個數字>>=
    <<分割欄位>>
    field_lines = [[int(field1), int(field2)] for field1, field2 in txt_field_lines]
    @

最後,分割欄位有很多方法,因為我們已經知道輸入的格式不會錯誤,所以用簡 單的 str.split() 來切割就可以了:

    <<分割欄位>>=
    txt_field_lines = [line.split() for line in lines]
    @

這篇 blog 經過 notangle 的處理,會變成以下的 python 程式:

    # notangle -R'接受字串的 function' literate-programming.mdwn
    def mul_n_sum(data):
        lines = data.strip().split('\n')
        txt_field_lines = [line.split() for line in lines]
        field_lines = [[int(field1), int(field2)] for field1, field2 in txt_field_lines]
        return sum([field1 * field2 for field1, field2 in field_lines])

可以跟 Thinker 的結果比比看 :)

這篇是故意使用類似的思考邏輯來寫,所以會產生相似的程式,且為了展示 LP 我最後還刻意調整了一下順序。只要換一下思考的方向,要寫出 loop 的版本也 是有可能的。重點在於隨想隨寫,邊寫程式邊紀錄思考過程。

碎碎唸的好處是不怕跳躍式的思考,想寫什麼就寫什麼,邊想邊寫,壞處是最後 組合出來的程式一般人可能沒辦法直接看懂,但是這樣寫還真的滿有趣的,有機 會一定要試試!

其他參考資料: