0 符號、演算、與抽象化

0.6 可演算的程式語言

正確性是如此重要又難以掌握的性質,我們因此希望程式語言能幫助我們確保程式的正確性。我們採用的方法是:給定一個規格,我們希望正確的程式能夠由其規格演算、推導出來。就如同在騎士與惡棍問題中,我們把希望 Q 滿足的性質寫下,然後利用符號的規則算出 Q. 我們希望程式也能如此從其規格被算出來。

以最大區段和問題為例,用我們之後將介紹的符號,我們可以把([RefUndefined])改寫成:

  mss = max . map sum . segments 

其意思大約是「給定一串數字,找出它的所有連續區段,算每一個區段的和,然後傳回其中最大的。」接下來的問題是,mss 有沒有比較快的實作?我們希望可以用代數方法,由 mss 的規格出發:

   max . map sum . segments
=    {- segments 的定義 -}
   max . map sum . concat . map inits . tails
=    {- 某定理 -}
   ...
=    {- 另一些定理 -}
   max . scanr oplus 0 

最後的 max . scanr oplus 0 相當於前一節的程式,只是以不同的符號表示。

我們可以由規格出發,在符號上操作,將程式如同求代數的解一樣地算出來。只要每個小步驟都正確,最後的程式就是正確的。這套技術稱作「程式推導(program derivation)」或「程式演算(program calculation)」,將是本書重要的主題。而在本書的觀點中,函數編程相較於其他典範的特色與優勢是函數語言是一套適合演算的符號系統。

適合演算的符號   我們舉個例子,談談符號「適合演算」是怎麼回事。 圖[RefUndefined]中顯示的是西元前四世紀左右希臘人使用的數字表示法。 以希臘字母表為順序,$1$, $2$,... 到 $9$ 分別寫成 α, β, ... θ. 每遇到 $10$ 的乘冪便換一組符號,如 $10$, $20$, ... 寫成ι, κ ...。 數值 $11$, $12$, $13$ 分別寫成 ια, ιβ, ιγ, $21$, $22$, $23$ 則寫成 κα, κβ, κγ. 數值 $653$ 可寫成χνγ. 這套表示法的缺點包括不易表示大數,以及不易做演算:我們需要知道 δ 加上 β 等於 ς, 以及 μ 加上 κ 等於 ξ。西元前八世紀,希臘人曾使用以 I, Π, Δ, H 分別表示 $1$, $5$, $10$, $100$ 的表示法。後來被圖[RefUndefined]中的系統取代。無論如何,希臘人仍以此發展出了進步的數學。

與之對比的是瑪雅數字。這套數字系統的年代不詳,據估計可能在西元四至世紀採用。一個點代表 $1$, 一條橫杠代表 $5$。 例如 $19$ 寫成 𝋳. 瑪雅數字採用 $20$ 進位,較大的位數寫在上方。因此

代表 , 也就是 $151$. 另有個例外:第三個數字為 $18$(而非 $20$)的乘冪 --- 因為 $18 \times 20 = 360$ 接近一年的日數。 圖 [RefUndefined] 中左邊由上至下是以瑪雅數字寫成的 $3212199$. 值得注意的是,此類以符號的位置表示其量級的數字表示法大都需要一個相當於「零」的佔位符號。 在瑪雅數字中,該符號寫成 𝋠。因此 $20$ 可寫成 .𝋠, 與只有一個點的 $1$ 區分。一些早期數字表示法中有此種佔位符號,但並不見得把「零」視為可獨立存在的一個數。「將零視為一個數字」是數學史上的重要發明,許多數學性質都需有它的存在才得以描述。程式語言中也有類似的情況:有一個「不做任何事的指令」或一個「將輸入照樣傳回的函數(即id)」對於描述程式的性質是很重要的。

如何用瑪雅數字作加法?以下顯示的是 $151 + 55$:

我們可先把兩個個位數(𝋫與𝋯)和兩個 $20$ 位數(𝋯與𝋢)分別相加。 其中,個位數相加的結果是 --- 超過了 $20$! 於是我們把其中四個橫槓(即一個 $20$)往上進位成一個點, 和 $20$ 位數的四個點結合成另一個橫槓。

有趣的是,我相信大部分的讀者即使可能是第一次看到這種數系,也從未使用過 $20$ 進位, 還是可迅速理解以上的加法,甚至可以舉一反三地自己試試看。 這就是一個適合演算的符號系統給我們的便利。

對本書來說,函數語言的價值便是 --- 它是個適於演算的語言。