第一章 導論
1.5 論文章節架構
國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
修改 AspectFun 的型態推導規則並擴充其資料型態宣告,使得 AspectFun 可以輔助實現 泛型程式設計,並能確保程式的型態安全。
1.3 論文之成果
本論文成果主要為下列兩項
使用 AspectFun 的剖面機制探討函數式語言上的程式重用與擴充性問題,
並說明如何以剖面機制解決程式不易擴充的問題。另外針對遞迴函數的重 用性問題,也指出以剖面機制 cflow 取代先前研究方法的優勢。
在 AspectFun 語言中輔助實現了泛型程式設計。為輔助實現泛型程式設 計,本研究擴充了 AspectFun 的資料型態宣告使其支援 Existen -tial Type,
並修改了 AspectFun 在多型遞迴函數上型態推導規則,以及修正 mutual recursion 所造成的 predicate 傳遞問題。
1.4 論文之限制
由於 AspectFun 不支援 type class 的機制,因此 AspectFun 編譯器無法編譯使用 type class 的函數,AspectFun 另有提供單型(monomorphic)函數替代多型(polymorphic)函數,例如 inteq、chareq 用來替代(==)。
另外 AspectFun 原始碼使用了 GHC 特有的擴充及函式庫,所以無法使用其它的 Haskell 編譯器進行編譯。AspectFun 原始碼僅限 GHC 6.8 之後的版本可以進行編譯。針 對本論文中第四章節所提及的 AspectFun 範例需要使用額外的 patch 才能完成編譯,其 餘的範例程式碼則不需使用。
1.5 論文章節架構
第二章介紹程式擴充性與重用性概念,以及本研究所使用的 AspectFun 語言之語法定 義、剖面織入規則、type-scoped advice、cflow 和實際應用範例。
第三章介紹程式語言處理 Expression Problem 的問題所在,以及重用遞迴函數程式
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
AspectFun 的 type-scoped advice 與 type class 的差異。
第四章介紹如何在 AspectFun 上輔助實現泛型程式設計,並說明在實現泛型程式設 計過程中遭遇的問題及其解決作法,最後並與相關函數式剖面導向語言研究 Aspect ML 比較成果差異性。
第五章總結本研究的成果及描述未來可進行的方向。
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
第二章 背景介紹
2.1 程式的可重用性 (reusability)及擴展性(extensibility)
程式語言的目標是希望提供模組化的程式建構方法以便程式設計者設計出重用性高且 具備可擴充性的程式碼,重用性高的程式碼是指程式碼不需修改和重新編譯即可被新程 式碼引用且正確運作,重用程式碼的好處是避免了程式設計者重複同樣的工作並且免於 錯誤,可讓程式設計者減少開發程式的時間;程式的擴充性可讓現存的系統容易增加新 功能模組,而新增的功能模組日後亦可繼續擴充。
在主流的物件導向語言中,強調將資料與其相關的運算函數封裝為一個模組單位,
藉由繼承(inheritance)及委派(delegation)來達到程式碼重用的目的。此外,近幾年來在物 件導向語言程式相關的程式擴充性研究中 Mixin[13]、Visitor Pattern 等也常被視為促進 程式碼重用的方法之一,Visitor Pattern 作法是將類別(class)中所有的方法(method)抽離 至外部的 Visitor 類別,一旦程式設計者希望父類別的方法有所擴充時,可以藉由繼承實 現擴充,Visitor Pattern 適用於資料不變動的狀況;而 Mixin 可視為繼承機制的延伸,它 允許程式的模組單位在定義時不需先指定其繼承的父類別)而是延後至該模組實際被使 用時才指定
在 Scala 語言中可使用其實現的 Mixin 的建構機制(在 Scala 中稱之為 trait 的模組化 建構單位);在函數式語言中,程式碼的重用通常是藉由高階函數(High order functions) 達成,高階函數是指一個函數接收其它函數作為引數或者將函數當作回傳值,常見的高 階函數如 map、fold 等。此外亦有許多相關研究探討如何在函數式語言中導入物件導向 語言的程式碼重用機制以便設計出更具擴充性的程式碼。
2.2 AspectFun 介紹
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
由於本研究目的之一在探討 AspectFun 此函數式剖面導向語言在程式擴充性與泛型程式 設計上的支援與應用,此章節將簡略說明 AspectFun 的語言特色並以範例介紹其剖面機 制及相關應用。
2.2.1 AspectFun 語言特色與基礎語法介紹
AspectFun 是一個多型(Polymorphic)及靜態型別(statically typed)的程式語言,其語 法近似於 Haskell 語言,在 AspectFun 程式中使用者可以定義多型函數並且在編譯過程 中會對程式進行型態推導檢查程式是否有型態錯誤問題。透過 AspectFun 編譯器可將以 AspectFun 定義的語法(圖 2.1)撰寫的程式碼轉換成標準可執行的 haskell 程式。
AspectFun 程 式 中 主要 分 為 三 個部 分 : 函數 宣 告 、 剖面 宣 告及程 式 主 體 (main expression)。函數宣告如同 Haskell 語言,剖面宣告由剖面名稱、Pointcut 以及剖面 定義(body)組成,Pointcut 為程式設計者指定剖面所會影響的函數,AspectFun 與 AspectJ 語言的剖面宣告有所不同,Pointcuts 宣告與 Advice 中的程式為一個完整結 構,不可各自獨立分開宣告。本語言僅能抓取在頂層宣告的函數或 advice,區域 let 定義之函數無法被 advice 影響,advice 僅影響頂層定義的函數。
圖 2. 1 : AspectFun Syntax
‧
保留字,其功能等同於 AspectJ 之 proceed(),用於呼叫此剖面所影響的函數(即範例中 的 f),而剖面的引數 arg 視為受影響函數的引數(即 f 定義中的引數 x)。-- aspect definition
n@adivce around {f} (arg) = proceed arg in
-- function definition f x = x in --main expression (f 2)
而在 AspectJ 常見的 before 與 after advice 都可透過 AspectFun 中的 around 去模 擬 :
my_before@adivce around {f} (arg) = { println “before f get called” ; proceed arg }in
2.2.2 Type-scoped advice
Type-scoped advice 為 AspectFun 所提供的另一種剖面宣告,其目的在限制 advice 是依 據引數的型態才被觸發 :
n1@advice around {f} (arg) = { println “arg is any type” ; proceed arg} in
n2@advice around {f} (arg :: [a]) = { println “arg is list” ;
‧
抽象時(general),此 advice 便會織入至函數中,一旦函數被呼叫時 advice 便會被觸 發。2.2.3 Cflow pointcut
AspectFun 也提供了類似 AspectJ 的 cflow 功能,cflow 是指程式執行時流程控制,其 目的近似於 type-scoped advice,限制程式在某個特定的執行流程下才會觸發 advice,
如 :
AspectFun 提供了相當具有彈性的 cflow 機制與語法,我們也可改寫原來的 pointcut 定義為 f – cflow(g),限制 advice 在 f 被呼叫時且 g 不被執行的狀況下才會觸發。
2.2.4 data+
基於對舊有程式的擴充需求,除了一般函數式語言的資料型態宣告方式,AspectFun 提
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
供了一特殊的資料型態宣告方式 data+,data+的目的在減少程式設計者在擴充新的資料 型態定義時直接修改舊有資料定義的程式碼 :
data Shape = Circle Int in
drawShape :: Shape -> ( )
drawShape (Circle r) = drawCircle r in
在上述範例中可見,程式碼中定義了一資料型態 Shape 及函數 drawShape,drawShape 可將不同的 Shape 輸出至螢幕顯示,由於目前只定義了一種 Shape 為 Circle,當程式設 計者想要對原有的程式繼續擴充時,使用 data+可以避免程式設計者直接修改舊有的資 料型態定義 :
data+ Shape = …| Square Int Int in
由於原有的資料型態 Shape 現在以被擴充增加了 Square,原有的函數定義也必頇同樣跟 著擴充,否則便會造成程式在執行時發生錯誤,在 AspectFun 中可以藉由宣告 advice 來 達到擴充函數定義的目的 :
square@advice around {drawShape} (arg) = case arg of
Square w h -> drawSquare w h _ -> proceed arg in 2.2.5 AspectFun 的織入系統介紹
AspectFun 是一個靜態織入的剖面導向語言,靜態織入是指程式在編譯過程中就要決定 advice 是否要被觸發,例如一個 AspectFun 程式 :
n@advice around {f} (arg) = proceed arg in f x = x in
g x = f x in (g 5, g „a‟)
‧
n proceed arg = proceed arg f x = x
g x = (n f) x
main = do putStrLn $ show $ (g 5, g „a‟)
其中 advice n 將會被轉換為一高階函數宣告,proceed 是高階函數的參數,目的在讓 advice 定義中所使用的 proceed 保留字可以被代換為 pointcut 中的函數,arg 參數的作用類似 proceed。在原先的 AspectFun 程式中,每次呼叫函數 f 將會觸發 advice n,即 n 會織入 至 f 中,在轉換後的 Haskell 程式碼的函數 g 定義中,((n f) x)即是表示 n 織入至 f 中,
而這就是 AspectFun 用來實現靜態織入的程式碼轉換方法。
除了一般的 advice 宣告,我們知道 AspectFun 也提供了 type-scoped advice 宣告控制 advice 的觸發條件,由於 type-scoped advice 必頇依據函數引數的實際型態判斷織入與 否,因此在編譯過程中無法直接將所有的函數 f 呼叫一律轉換為(n f) ,如 :
n@advice around {f} (arg :: Char) = proceed arg in f x = x in
g x = f x in (g 5, g „a‟)
在上述程式中,由於 g 的型態是 a -> a,而 advice n 只有當引數為 Char 型態時才會被觸 發,在推導至 g 時,我們只知道 g 的引數是某個任意型態 a,無法判斷此時呼叫 f 是否 會觸發 advice,因此我們需要型態資訊來輔助判斷函數是否要被織入,而 predicate 就是 AspectFun 在輔助用來判斷 advice 是否織入的額外型態資訊。
當程式中使用 type-scoped advice 時,由於某些函數定義的型態較 type-scoped advice 定義的型態較為抽象,在推導至此函數無法立即判斷織入與否,此時函數會形成 predicate,直到推導至其他函數或程式主體確定型態資訊足夠時,才判斷是否織入。在 前述程式中,由於 f 的型態是 a -> a,暫時無法判斷 n 是否織入,此時 f 會形成 predicate,
而 g 的定義中會呼叫 f,因此 g 會帶著 predicate f,直到推導至程式主體時確定 f 的引數
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
值是為 Char 型態,才對 f 進行織入。此 AspectFun 程式最後會被轉換為下列可執行的 Haskell 程式碼 :
n proceed arg = proceed arg f x = x
g df x = df x
main = do putStrLn $ show $ (g f 5 , g (n f) „a‟)
其中 g 帶著 predicate f,因此原來的函數 g 定義會被轉換為 g df x = df x,在推導至程式 主體中的 g „a‟時知道 f 會觸發 advice n,便傳遞織入過的 f 函數(n f);在程式主體中的 g 5 不符合 n 被觸發的條件,因此只傳遞未被織入的 f。
‧ 3.1 The Expression Problem
對於各程式語言不同的程式建構方法,我們需要一個公認有效的衡量標準(benchmark) 來評估該程式語言程式建構機制的優劣,而 Expression Problem 就是個經常被用來衡量 程式語言建構機制優劣的問題。Expression Problem 是指在實作一個程式語言時,因為 data 及 operation 兩個方向在擴充的過程中會因為程式語言的擴充機制受限,導致程式設 計者需要修改舊有程式碼的問題。Expression Problem 所要解決的問題在於:
擴充新的 data / operation 時,舊有的程式碼不需修改和重新編譯, 新的程式 碼不會重複舊有程式碼。
程式碼在通過編譯器的型態推導系統後,在執行期間(runtime)不會產生型態錯 誤。
在物件導向語言中,我們透過繼承便可輕易擴充新的 data,而擴充新的 operation 卻需要修改所有相關的類別(class),而 Visitor Pattern 的作法是將類別中的方法( Method) 抽離至外部成為 Visitor 介面的一種設計方法,針對需要新增的 operation 程式設計者只 需定義新的類別並實作 Visitor 介面即可擴充 operation,在 data 不變動的前提下使用 visitor pattern 有助於程式的模組化建構,但使用 Visitor Pattern 也相對限制了物件導向語 言擴充 data 的簡易性;而函數式語言則與物件導向語言相反,擴充 operation 容易但擴
在物件導向語言中,我們透過繼承便可輕易擴充新的 data,而擴充新的 operation 卻需要修改所有相關的類別(class),而 Visitor Pattern 的作法是將類別中的方法( Method) 抽離至外部成為 Visitor 介面的一種設計方法,針對需要新增的 operation 程式設計者只 需定義新的類別並實作 Visitor 介面即可擴充 operation,在 data 不變動的前提下使用 visitor pattern 有助於程式的模組化建構,但使用 Visitor Pattern 也相對限制了物件導向語 言擴充 data 的簡易性;而函數式語言則與物件導向語言相反,擴充 operation 容易但擴