2.13 詞典序歸納
我們終於要定義第1.9 節與2.12 節中都提到的「合併」函數:將兩個已排序好的串列合而為一。
函數 merge 最自然的寫法可能是:
merge :: List Int -> List Int -> List Int
merge [] ys = ys
merge (x:xs) [] = x:xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys)
else y : merge (x:xs) ys
如果兩個串列之中有一個為空串列,合併的結果是另一個。 如果兩個都不是空串列,我們比較其第一個元素,以便決定將哪個當作合併後的第一個元素。
但,merge 最後一個子句的第一個遞迴呼叫中,y:ys 沒有變短;第二個遞迴呼叫中,x:xs 沒有變短。
這種程式會終止嗎?如果會,是哪種歸納定義呢?
一種看法是將 merge 視作和例2.20 中的 interleave 類似的歸納定義:兩個參數的長度和在遞迴呼叫中變小了。另一個可能是將函數 merge 視作詞典序歸納(lexicographic induction)的例子 --- 詞典序歸納也是良基歸納的一個特例。
先介紹詞典序。我們怎麼決定兩個英文單字在詞典中的先後順序呢?
通常是先比較其第一個字母,如果第一個字母便分出了大小,就以此大小為準,不論剩下的字母為何。如果第一個字母一樣,便從第二個字母開始比起。
若要以形式化的方式寫下詞典序的定義,我們考慮一個較簡單的狀況:如何比較 x1 y1 和 x2 y2 兩個長度均為二的字串?如果 (<) 是比較單一字元大小的順序,我們把兩個字元的詞典序寫成 $(<;<)$,定義如下:
x_1 y_1 (<;<) x_2 y_2 ~~\equiv~~
x_1 < x_2 ~\vee~ (x_1 = x_2 \wedge y_1 < y_2) 如前所述,先比較 x1 與 x2, 如果相等,再比較 y1 與 y2.
我們可以再稍微擴充一些,考慮 $x_i$ 與 $y_i$ 型別不同的情況:
定義 2.22
給定義在型別 A 之上的序 $(\triangleleft)$ 和型別 B 之上的序 $(\prec)$, 它們的詞典序(lexicographic ordering),寫做 $(\triangleleft;\prec)$,是 (A × B) 上的一個序,定義為:
(x_1, y_1) (\lhd;\prec) (x_2,y_2) ~~\equiv~~
x_1 \lhd x_2 ~\vee~ (x_1 = x_2 \wedge y_1 \prec y_2) 上述定義也可擴充到三個、四個... 元素的序對上。此處便不把他們寫出來了。
關於詞典序的有趣性質相當多,此處僅用到下述性質
定理 2.23
如果 $(\triangleleft)$ 與 $(\prec)$ 均為良基序,$(\triangleleft;\prec)$ 也是良基序。
因此,$(\triangleleft;\prec)$ 也可用來做歸納定義。 我們把使用詞典序的良基序歸納稱作「詞典序歸納」。
回頭看 merge 的定義。先考慮下述、在第1.9 節中出現的 uncurried 版本:
merge' :: (List Int × List Int) -> List Int
merge' ([], ys) = ys
merge' (x:xs, []) = x:xs
merge' (x:xs, y:ys) = if x <= y then x : merge' (xs, y:ys)
else y : merge' (x:xs, ys)
如果 (<:) 是比較串列長度的良基序,我們可說 merge' 是在 (<:;<:) 之上的歸納定義。確實,
(xs, y:ys) (<:;<:) (x:xs, y:ys), 因為xs <: x:xs;(x:xs, ys) (<:;<:) (x:xs, y:ys), 因為x:xs = x:xs且ys <: y:ys.
至於 merge 則是 merge' 的 curried 版本,因此也是定義良好的。
如前所述,函數 merge 的定義不一定得看成辭典序歸納 --- 它也可和 interleave 一樣看成另一種較簡單的良基歸納 --- 比較兩參數的長度之和。
接下來的例子就得倚靠辭典序歸納了。
例 2.24
知名的 Ackermann 函數 (Ackermann's function) 是一個遞增得相當快的函數。
ack :: Nat -> Nat -> Nat
ack 0 n = Suc n
ack (Suc m) 0 = ack m 1
ack (Suc m) (Suc n) = ack m (ack (Suc m) n)
該函數定義上的特殊處之一是 ack (Suc m) n 的結果又被當作 ack m 的參數,因此較難以用理解 interleave 的方式理解。
但它可以視為詞典序 (<;<) 上的歸納:
(m,1) (<;<) (Suc m,0),因為m < Suc m;(Suc m, n) (<;<) (Suc m, Suc n),因為n < Suc n;(m, ack (Suc m) n) (<;<) (Suc m, Suc n),因為m < Suc m.