4 關於語意的基本概念

4.1 指稱語意

指稱語意(denotational semantics)談一個程式是什麼。 在我們的討論範圍中,有堅實基礎、有不含糊的定義的東西只有數學物件。 因此,指稱語意試圖把程式語言對應到數學物件。 最簡單的指稱語意可能是基於集合論的:把型別視為集合,把程式語言中的函數視為集合論中的函數。 例如,Nat 便是自然數的集合;(A * B) 是 A 與 B 的笛卡兒積: 在更嚴謹的說法中,我們通常用一個語意函數$\llbracket\_\rrbracket$將語法對應到其意義。因此我們會說 $\llbracket |A*B| \rrbracket = \llbracket |A| \rrbracket \times \llbracket |B| \rrbracket$. 此處採用較不正式的說法。

    (A * B) ~&= \{ (a,b) \mid a \in A, b \in B \} 

但函數又是什麼呢?集合論中,一個型別為 A -> B 的函數可視為 A * B 的一個子集;若 f x = y,該子集中便有 (x,y) 這個元素。例如,double :: Nat -> Nat 可表示成如下的集合:

   \{(0,0), (1,1), (2,4), (3,6), (4,8) \ldots \} 

能稱為函數的集合還需滿足兩個額外條件:

  • 簡單性(simplicity): 對所有 $x \in |A|$, 該集合中僅存在一對唯一的 (x,y)。意即每個輸入只對應到一個輸出。

  • 完整性(totality): 對所有 $x \in |A|$, 在該集合中都存在某對 (x,y)。意即值域中的每個元素都被涵蓋到。

至於遞迴函數呢?以階層 fact 為例,其定義為:

fact :: Nat -> Nat
fact Zero     = 1
fact (Suc n)  = (Suc n) * fact n 

我們可想成:有一個從集合到集合的函數 factF,

  \Varid{factF}~X ~=~ \{(Zero,1)\} \cup \{(Suc n, (Suc n) \times m) \mid (n,m) \in X \} 

給任何集合 X,factF 傳回這樣的集合:

  • 新集合中有 (Zero,1) --- 這對應到 fact Zero = 1.

  • 對 X 之中的每一個 (n,m), 新集合中有 (Suc n, (Suc n) * m) --- 這對應到 fact (Suc n) = (Suc n) * fact n.

而函數 fact 的語意就是 factF 唯一的定點 (fixed point). 意即 fact 是唯一滿足 fact = factF fact 的集合。 關於定點的較完整理論可參照第 2.9 節。 確實,fact 可寫成集合如下:

 fact = {(0,1),(1,1),(2,2),(3,6),(4,24)...} 

若將 fact 餵給 factF,

   factF fact
=  {(0,1)} ∪ {(Suc n, (Suc n) * m) | (n,m) `elem` {(0,1),(1,1),(2,2),(3,6)...}}
=  {(0,1)} ∪ {(1,1),(2,2),(3,6),(4,24)...} 

我們又得到了 fact. 因此 fact 確實是 factF 的定點。

但,要把「fact 便是 factF 的定點」當作 fact 的定義,我們還得確定:確實只有這麼一個集合滿足 fact = factF fact。 讀者稍加檢查一下,即可發現確實如此 --- 例如,把 fact 添一項或刪一項,都會使得 factF fact 不等於 fact.

並不是所有從集合到集合的函數都有唯一的定點。考慮如下兩個函數:

g 0  = 0
g x  = g (Suc x) 
h 0  = 0
h x  = (-1) * h x 

若以類似 fact 的方式試圖將它們寫成某函數的定點,我們可能寫出如下的 gF 和 hF:

gF X  = {(0,0)} ∪ {(n,m) | (Suc n,m) `elem` X}  
hF X  = {(n,(-1*m)) | (n,m) `elem` X} 

但 gF 的定點有

  {(0,0),(1,1),(2,1),(3,1)...} 
  {(0,0),(1,2),(2,2),(3,2)...} 
  {(0,0),(1,10),(2,10),(3,10)...} {-"~~..."-}

等無限多個。而 hF 有一個唯一定點:空集合,但空集合違反了前述的完整性要求。

如同 g 和 f 這樣的函數,在我們目前介紹的簡單指稱語意中是沒有定義的。 它們沒有語意,在我們的語言中是不該有的存在。 它們剛好也是執行起來不會終止的函數。 因此,我們似乎可以猜測「有唯一定點」和「執行會終止」似乎有些關聯。 但,如前所述,我們的指稱語意中沒有「執行」的觀念。 那是操作語意擅長描述的。

值得一提地,指稱語意採取了一個靜態的世界觀。 一個函數單純地是輸入與輸出的對應,沒有「執行」的觀念,沒有執行快慢的考量,也沒有預設「終止」的概念。

函數 double1 x = x + x 與 double2 x = 2 * x 使用不同的演算法,但它們的語意都是同一個集合,因此被視為同一個函數 --- 在語意上我們無法區分 double1 與 double2。

在指稱語意中,當我們說兩個函數相等,意指它們的語意是同一個集合。 例如,map Suc . concat = concat . map (map (Suc)), 因為兩者的語意都是下述的集合:

 {([],[]), ... ([[1],[2]], [2,3]),... ([[1],[2,3]],[2,3,4]), ...}