2.5 從資料、程式、到證明
本節再用一個例子談談「讓符號為我們工作」。考慮證明下述性質:
sum . concat = sum . map sum (2.3 )
根據外延相等,這相當於證明對所有xss,
sum (concat xss) = sum (map sum xss).
當 xss := [], 等號兩邊都歸約成 0. 考慮 xss := xs :xss 的情況:
sum (concat (xs:xss))
=== {- concat 之定義 -}
sum (xs ++ concat xss)
=== {- 因 sum (xs ++ ys) = sum xs + sum ys -}
sum xs + sum (concat xss)
=== {- 歸納假設 -}
sum xs + sum (map sum xss)
=== {- sum 之定義 -}
sum (sum xs : map sum xss)
=== {- map 之定義 -}
sum (map sum (xs:xss))
讀者對這個證明最大的疑問可能是:我們怎麼知道該用 sum (xs ++ ys) = sum xs + sum ys 呢?
為什麼當我們遇到 sum (xs ++ concat xss), 我們不是把 xs 再拆成首和尾,甚至把 xss 拆開?
這是許多綜合考量的結果。
首先,我們預期會使用歸納假設,因此證明前幾步的目的均為把 sum 推到 concat xss 左側,試圖做出 sum (concat xss).
此處我們再強調一個觀念:證明中的步驟不是瞎猜的,而是有目的的。
符號給我們提示:我們需要把 sum 往右推,因此我們試圖尋找能完成這個目標的性質。
其次,證明的結構跟隨著程式的結構。由於 concat 是由 (++) 定義的,每一個關於 concat 的定理,均很有可能奠基在一個關於 (++) 的相對定理上。為了證明一個描述 sum 與 concat 的關係的定理,我們可能會需要一個關於 sum 與 (++) 的性質。
有了符號給我們的這兩個線索,我們便可以運用我們對語意的了解:sum 與 (++) 應該會滿足什麼性質?
我們可猜測應該是 sum (xs ++ ys) = sum xs + sum ys --- 根據我們對 sum 與 (++) 的語意上的理解,這性質成立的機會很大。
而且,這個性質允許我們把 sum 推到右邊。
符號仍舊幫助了我們。 我們不可能期待所有定理都只靠符號推導出,在關鍵時刻我們仍會需要對於特定領域的、語意上的知識。但符號給了我們許多引導, 縮小我們需要猜測的範圍。
至於 sum (xs ++ ys) = sum xs + sum ys 該怎麼證明呢?
不要急著把 xs 與 ys 都拆開。
觀察等號左右邊的式子,根據 (++), (+) 與 sum 的定義,兩個式子的歸約都是先對 xs 做情況分析。
因此我們可試著在 xs 上做歸納。
證明的結構跟隨著程式的結構 --- 這是做證明時相當好用的指引。
程式對哪個參數做歸納,我們做證明時就在那個參數上做歸納。
函數 f 用函數 g 定義,我們證明 f 的性質時就可以預期會需要一個 g 的相關性質。
並非所有證明都可以如此做出,但這個模式在許多情況下都適用。
而如我們所知,程式的結構又跟隨著資料的結構:
例如,串列有 [] 和 x:xs 兩個情況,我們定義串列上的程式時也分出這兩個情況,
定義 f (x:xs) 時可以使用 f xs 的值。
資料結構、程式、與證明於是有著這樣的連續關係:
歸納定義出一個資料結構,我們就有了一個在上面歸納定義程式的方法;
有了歸納定義的程式,我們便知道怎麼做關於它的歸納證明。
一切由資料結構開始。
練習 2.12
證明 length . concat = sum . map length.
我們會需要什麼關於 (++) 的相關性質?
答案
考慮 xs:xss 的情況:
length (concat (xs:xss))
=== length (xs ++ concat xss)
=== {- 因 length (xs ++ ys) = length xs + length ys -}
length xs + length (concat xss)
=== {- 歸納假設 -}
length xs + sum (map length xss)
=== sum (length xs : map length xss)
=== sum (map length (xs:xss))
我們需要的性質是 length (xs ++ ys) = length xs + length ys .
練習 2.13
證明對所有 f, map f . concat = concat . map (map f).
我們會需要什麼關於 (++) 的相關性質?
答案
考慮 xs:xss 的情況:
map f (concat (xs:xss))
=== map f (xs ++ concat xss)
=== {- 因 map f (xs ++ ys) = map f xs ++ map f ys -}
map f xs ++ map f (concat xss)
=== {- 歸納假設 -}
map f xs ++ concat (map (map f xss))
=== concat (map f xs : map (map f xss))
=== concat (map (map f (xs : xss)))
我們需要的性質是 map f (xs ++ ys) = map f xs ++ map f ys .