第四章 以 AspectFun 輔助實現泛型程式設計
4.3 以 AspectFun 輔助實現的泛型程式設計
國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
用 Existential type,型態推論系統為了確保型態安全只能推導出較抽象的型態資訊,因 此在 strings‟定義中呼叫 strings 時推導系統只能判斷出此時傳給 strings 的引數型態是某 個未知型態 a 而非具體的 String 型態,導致 advice n 不會被觸發,欲使 AspectFun 可以 輔助實現泛型程式設計,我們需要解決 Existential type 造成型態資訊不足問題,使得 advice n 可以被織入至 strings。
4.3 以 AspectFun 輔助實現的泛型程式設計
在 AspectFun 中,為了解決 Existential type 造成的型態資訊不足問題,我們採用的作法 是將型態資訊保留至程式執行期間,讓程式設計者可以在執行期間自行根據型態選擇運 算工作,而非依賴編譯器藉著不足的型態資訊決定:
n@advice around {strings} (arg) = String? [arg]
Others? proceed arg
在靜態型別語言中保留型態資訊的作法在讓程式中所有的運算值都額外攜帶一個型態 標籤如 :
在函數式語言中,我們可以很容易的藉由資料型態定義來編碼表示這類帶有型態標籤的 值 :
data Typed a = Typed a (TypeRep a)
data TypeRep a = Rint | RChar | forall b. RList (TypeRep b)
資料型態 Typed 用來表示一數值與其型態的配對,TypeRep 用來表示此型態標籤,例如 5 (Typed 5 RInt)
„a‟ (Typed „a‟ RChar)
5 Int
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
[1,2,3] (Typed [1,2,3] (RList (RInt)))
使用 Typed 與 TypeRep 編碼方法,將型態資訊以值的方式保留下來,使得程式可以使用 這些型態資訊,我們便可以藉由這些保留下來的型態資訊,在程式執行期間自行依據型 態資訊判斷某個被推導為 Existential type 的值其確切型態為何,彌補靜態型別語言的不 足。
因應此型態保留方法的導入原來的 toSpine 與 strings 函數型態也必頇更改為 : strings :: Typed a -> [String]
toSpine :: Typed a -> Spine a
strings 與 toSpine 函數定義便可根據引數所攜帶的型態資訊分配的不同的運算工作上。
而原來的 Spine a 定義也必頇將 b 更改為 Typed b :
data Spine a = … | forall b. App (Spine (b->a)) (Typed b)
此外基於程式的型態安全問題,為了避免使用端將某個值與錯誤型態配對而成一 Typed 值,如(Typed „a‟ RInt)即為一個配對錯誤範例,配對錯誤將會導致程式發生不可預期且 不易除錯的型態錯誤問題,為了避免此問題我們額外引用了 Equivalence types,對原有 的 TypeRep 定義也進行了部分修改 : :
data TypeRep a = RInt (Equiv a Int) |RChar (Equiv a Char)
|forall b. Rlist (Rep b) (Equiv a [b])
data Equiv a b = Equiv (a -> b) (b -> a)
原來的 TypeRep 的建構函數都額外攜帶了一引數(Equiv …),Equiv a b 表示型態 a 與型 態 b 兩者是相等的,且 a 和 b 可以進行轉換,在使用端我們可用來判斷一 Typed 的值與 型態的配對是否正確,如
(Typed 1 (Rint equiv))
‧
cast (Typed 1 (RInt equiv)) (RInt equiv)
cast success, return (Just 1, ….)
cast (Typed [1,2,3] (RList (RInt equiv) equiv) (Rlist (Rchar equiv) equiv)
cast fail, return Nothing
針對 List 的資料型態,我們也透過一類似 cast 函數的 castList 函數判斷一引數是否實際 為一串列 :
castList :: Typed a -> Maybe (IsList a)
此兩個轉型函數 cast 與 castList 在轉型失敗時皆會回傳 Nothing 表示,確保了程式在執 行期間的轉型失敗造成的型態錯誤。
在 4-2-2 節中我們已知 strings 的錯誤結果是由於 advice n 無法被觸發所造成,藉由 型態資訊的保留,程式已不需要藉由 type-scoped advice 去判斷引數型態,而是讓 advice n 在每一次 strings 被呼叫時直接觸發,並嘗試轉型判斷引數的型態是否為 String,若為 String 則回傳包含引數的串列,若轉型失敗,即引數型態不為 String,則交由原來的 strings 函數進行 Spine 轉換 :
‧
n@advice around {strings} (arg) = case cast arg (Rlist (Rchar equiv) equiv) of { Just (b, _, _) -> [b] ;
_ -> proceed arg } in
而最後的實際執行結果可以發現,藉由型態程式化與轉型函數的方法,使得靜態型別語 言 AspectFun 得以克服型態資訊問題並輔助實現了泛型程式設計 :
strings (Typed 1 (Rint equiv))
[ ]
strings (Typed "ab" (Rlist (Rchar equiv) equiv))
[ “ab” ]
strings (Typed ["ab", "cd"] (Rlist (Rlist (Rchar equiv) equiv) equiv)))
[“ab”, “cd”]
雖 然 AspectFun 和 Haskell 都是靜態型別語言而有型態資訊不足問題,套用 AspectFun 解決作法也可處理在 Haskell 上的問題,然而使用 AspectFun 的優勢在於剖面 語言的 proceed 機制所帶來的串連特性,假設未來我們需要對 strings 函數增加對 Ascii 的額外處理,在 AspectFun 中,我們只需要增加 advice 即可 :
n2@advice around {strings} (arg) = case cast arg (Rascii equiv) of { Just (b, _, _) -> …..(略) ;
_ -> proceed arg } in
但是在 Haskell 中,使用 type class 並不允許 instance 針對某一相同的型態進行重覆的宣 告情況 :
instance Strings (Typed a) where strings x = …. -- for String
instance Strings (Typed a) where -- duplicated, invalid strings x = …… -- for Ascii
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
函數定義,修改舊有程式碼的行為會破壞程式的重用性與擴充性原則。
instance Strings (Typed a) where strings x = String? do A
Ascii? do B -- insert code here Others? do C