行政院國家科學委員會專題研究計畫 成果報告
程式之模組性與擴充性:驗證方法與工具--子計畫五:剖面
導向函數語言之模組化狀態處理與型態擴充研究(第 3 年)
研究成果報告(完整版)
計 畫 類 別 : 整合型
計 畫 編 號 : NSC 97-2221-E-004-001-MY3
執 行 期 間 : 99 年 08 月 01 日至 100 年 10 月 31 日
執 行 單 位 : 國立政治大學資訊科學系
計 畫 主 持 人 : 陳恭
共 同 主 持 人 : 莊庭瑞、王柏堯
計畫參與人員: 碩士班研究生-兼任助理人員:林佳瑩
碩士班研究生-兼任助理人員:蕭名宏
碩士班研究生-兼任助理人員:黃于育
碩士班研究生-兼任助理人員:江尚倫
碩士班研究生-兼任助理人員:王啟典
碩士班研究生-兼任助理人員:陳政宏
碩士班研究生-兼任助理人員:黃文楷
碩士班研究生-兼任助理人員:李明憲
大專生-兼任助理人員:麻立恒
大專生-兼任助理人員:李致緯
報 告 附 件 : 出席國際會議研究心得報告及發表論文
公 開 資 訊 : 本計畫涉及專利或其他智慧財產權,1 年後可公開查詢
中 華 民 國 100 年 12 月 17 日
中 文 摘 要 : 剖面導向程式設計(Aspect-Oriented Programming, AOP)是
近年來繼物件導向程式設計(OOP)後,所興起的一種新的模組
化程式設計方法。從 AOP 的觀點來看,應用程式除了功能邏
輯以外,還有許多像安全需求等的橫跨性關注(crosscutting
concerns);實現這些橫跨性關注的程式碼應該要從功能模組
中分離出來,自成一模組並稱之為剖面。剖面與功能模組之
間的界接點由所謂橫切點(pointcut)來定義,並透過稱之為
織入(weaving)的機制將剖面程式碼(advice)整合入功能模組
中,從而合成完整程式,滿足系統整體需求。這樣實現橫跨
性關注的程式碼就可以集中封裝於適當的模組中,避免掉程
式碼糾結與重複的問題。
先前我們的國科會研究計畫(NSC95-2221-E-004-004-MY2)
以型態導向的方法,發展了一套多型剖面的織入技術,並據
以設計實作了一個實驗性的剖面導向函數語言 AspectFun。
本計劃在 AspectFun 語言既有的基礎上,進一步探討剖面機
制在函數式語言的程式模組化與擴充性方面的一些課題,所
要進行的探討涵蓋實作、語言設計與理論模型與驗證各個面
向,主題則大致上可分為三部份: (1) AspectFun 語言核心
機制的擴充與實作,以奠定後續研究的基礎。(2)探討如何以
剖面與編譯技術來模組化純粹函數語言內最基本的橫跨性關
注:狀態處理 (state manipulation via assignment) ,將
設計與實作具備狀態處理功能的剖面 (side-effecting
aspects)來處理的狀態改變,以提高純粹函數程式的模組
化。(3)探討如何以剖面處理函數語言型態擴充(type
extension)的需求,評估剖面導向的解決方案在程式擴充性
方面的適用性。
我們的成果簡述如下:在以剖面處理函數語言型態擴充(type
extension)的需求方面,我們開發出了好幾個可以增加
AspectFun 語言表達能力的機制與設施,也將這些機制實作
於 AspectFun 語言中,不過也發現到它們的限制:個別編譯
的困難。在以剖面與編譯技術來模組化純粹函數語言內的狀
態處理方面,我們發展了具備狀態處理功能的剖面
(side-effecting aspects)與 monadification 程式轉換演算法,不
僅可以處理一般程式,對於應用惰性求值的函數式語言也可
適用,可以順利處理的狀態改變,提高純粹函數程式的模組
化。我們也將這套機制與轉換技術實作於 AspectFun 的編譯
器,並將其發布於中研院的開放源碼平台 Open Foundry。此
外,我們定義了 side-effecting aspects 的操作型與語義,
驗證我們的程式轉換與織入方法的正確性。
中文關鍵詞: 剖面導向程式設計,多型剖面,織入,模組性,型態擴充、
函數語言、狀態處理
英 文 摘 要 : Aspect-oriented programming (AOP) aims at
modularizing concerns such as profiling and security
that crosscut the components of a software system.
Recently we have developed an aspect-oriented
functional language, AspectFun, for investigating
various issues in introducing aspect orientation to a
polymorphically typed functional language. In this
project, we carry on our work of AspectFun. Besides
extending AspectFun’s core mechanism for future
research, we investigated two issues of using aspects
for enhancing program modularity and extensibility.
First, in a purely functional language, a basic
crosscutting concern is ’side effect’, i.e., state
manipulation via assignments. Indeed, even we can
hide the hairy details of manipulating state by using
the state-of-the-art technology of monadic
assignments; we have to do a comprehensive rewriting
of our functional programs. Unfortunately, typical
aspects such as profiling and tracing are most
naturally implemented using side effects. If we
cannot handle side effects directly in an aspect, the
modularity acquired through aspects will be greatly
compromised. Therefore, we extended AspectFun with
side-effecting aspects which support state
manipulation mechanisms such as user-defined
variables and assignments directly. On the
implementation side, we developed some compilation
techniques, which transform the pure-functional part
into a monadic style, and weave in the aspects.
Furthermore, we devised a caching mechanism to
preserve the lazy semantics of AspectFun while being
able to execute side-effecting aspects in a
non-interfering way.
Second, we investigated the feasibility of using
aspects to address the type extension requirements as
stated in the famous expression problem (which calls
for language mechanisms that can support type-safe
types and associated operations, yet neither code
duplication nor code rewriting is required.) Here we
extended AspectFun with an inter-type declaration
mechanism, following the spirit of that of AspectJ,
to support type extension in a modular manner.
Specifically, when a data type is extended with some
new variants, we can employ aspects to define the
required extension for the operations associated with
the data type. Consequently, the entailed extensions
of those operations can be modularly encapsulated in
aspects. However, the separate compilation issue
remains a problem for future exploration.
英文關鍵詞: Aspect-Oriented Programming, Polymorphic Aspects,
Weaving, Type Extension, State handling
I
關鍵詞:剖面導向程式設計,多型剖面,織入,模組性,型態擴充、函數語言、狀態處理
剖面導向程式設計(Aspect-Oriented Programming, AOP)是近年來繼物件導向程式設計(OOP)後,所
興起的一種新的模組化程式設計方法。從 AOP 的觀點來看,應用程式除了功能邏輯以外,還有許多
像安全需求等的橫跨性關注(crosscutting concerns);實現這些橫跨性關注的程式碼應該要從功能模組
中分離出來,自成一模組並稱之為剖面。剖面與功能模組之間的界接點由所謂橫切點(pointcut)來定
義,並透過稱之為織入(weaving)的機制將剖面程式碼(advice)整合入功能模組中,從而合成完整程式,
滿足系統整體需求。這樣實現橫跨性關注的程式碼就可以集中封裝於適當的模組中,避免掉程式碼糾
結與重複的問題。
先前我們的國科會研究計畫(NSC95-2221-E-004-004-MY2)以型態導向的方法,發展了一套多
型剖面的織入技術,並據以設計實作了一個實驗性的剖面導向函數語言 AspectFun。本計劃在
AspectFun 語言既有的基礎上,進一步探討剖面機制在函數式語言的程式模組化與擴充性方面的一些
課題,所要進行的探討涵蓋實作、語言設計與理論模型與驗證各個面向,主題則大致上可分為三部份:
(1) AspectFun 語言核心機制的擴充與實作,以奠定後續研究的基礎。(2)探討如何以剖面與編譯技術來
模組化純粹函數語言內最基本的橫跨性關注:狀態處理 (state manipulation via assignment) ,將設計
與實作具備狀態處理功能的剖面 (side-effecting aspects)來處理的狀態改變,以提高純粹函數程式的模
組化。(3)探討如何以剖面處理函數語言型態擴充(type extension)的需求,評估剖面導向的解決方案在
程式擴充性方面的適用性。
我們的成果簡述如下:在以剖面處理函數語言型態擴充(type extension)的需求方面,我們開發出
了好幾個可以增加 AspectFun 語言表達能力的機制與設施,也將這些機制實作於 AspectFun 語言中,
不過也發現到它們的限制:個別編譯的困難。在以剖面與編譯技術來模組化純粹函數語言內的狀態處
理方面,我們發展了具備狀態處理功能的剖面 (side-effecting aspects)與 monadification 程式轉換演算
法,不僅可以處理一般程式,對於應用惰性求值的函數式語言也可適用,可以順利處理的狀態改變,
提高純粹函數程式的模組化。我們也將這套機制與轉換技術實作於 AspectFun 的編譯器,並將其發布
於中研院的開放源碼平台 Open Foundry。此外,我們定義了 side-effecting aspects 的操作型與語義,驗
證我們的程式轉換與織入方法的正確性。
II
Keywords: Aspect-Oriented Programming, Polymorphic Aspects, Weaving, Type Extension, State handling
Aspect-oriented programming (AOP) aims at modularizing concerns such as profiling and security that
crosscut the components of a software system. In AOP, a program consists of many functional modules and
some aspects that encapsulate the crosscutting concerns. An aspect provides two specifications: A pointcut,
comprising a set of functions, designate when and where to crosscut other modules; and an advice, which is a
piece of code, that will be executed when a pointcut is reached. The complete program behavior is derived by
some novel ways of composing functional modules and aspects according to the specifications given within
the aspects. This is called weaving in AOP. Weaving results in the behavior of those functional modules
impacted by aspects being modified accordingly.
Recently we have developed an aspect-oriented functional language, AspectFun, for investigating various
issues in introducing aspect orientation to a polymorphically typed functional language. In this project, we
carry on our work of AspectFun. Besides extending AspectFun’s core mechanism for future research, we
investigated two issues of using aspects for enhancing program modularity and extensibility. First, in a purely
functional language, a basic crosscutting concern is “side effect”, i.e., state manipulation via assignments.
Indeed, even we can hide the hairy details of manipulating state by using the state-of-the-art technology of
monadic assignments; we have to do a comprehensive rewriting of our functional programs. Unfortunately,
typical aspects such as profiling and tracing are most naturally implemented using side effects. If we cannot
handle side effects directly in an aspect, the modularity acquired through aspects will be greatly compromised.
Therefore, we extended AspectFun with side-effecting aspects which support state manipulation mechanisms
such as user-defined variables and assignments directly. On the implementation side, we developed some
compilation techniques, which transform the pure-functional part into a monadic style, and weave in the
aspects. Furthermore, we devised a caching mechanism to preserve the lazy semantics of AspectFun while
being able to execute side-effecting aspects in a non-interfering way.
Second, we investigated the feasibility of using aspects to address the type extension requirements as
stated in the famous expression problem (which calls for language mechanisms that can support type-safe
program extension in both the dimensions of data types and associated operations, yet neither code
duplication nor code rewriting is required.) In general, it is easier to extend the set of operations than to extend
the elements of a data type in a functional program. (By contrast, it is the opposite in an object-oriented
program.) Here we extended AspectFun with an inter-type declaration mechanism, following the spirit of that
of AspectJ, to support type extension in a modular manner. Specifically, when a data type is extended with
some new variants, we can employ aspects to define the required extension for the operations associated with
the data type. Consequently, the entailed extensions of those operations can be modularly encapsulated in
aspects. However, the separate compilation issue remains a problem for future exploration.
III
目錄
一、前言
... 1
二、研究目的
... 1
三、文獻探討
... 3
四、研究方法
... 4
五、結果與討論
... 8
六、參考文獻
... 10
附錄一、期刊論文
...
附錄二、期刊論文
...
剖面導向程式設計(Aspect-Oriented Programming, AOP) [1]是近年來繼物件導向程式設計(OOP)
後,所興起的一種新的模組化程式設計方法。從 AOP 的觀點來看,應用程式除了功能邏輯以外,還
有許多像安全需求等的橫跨性關注(crosscutting concerns);實現這些橫跨性關注的程式碼應該要從功
能模組中分離出來,自成一模組並稱之為剖面(aspect)。剖面與功能模組之間的界接點由所謂橫切點
(pointcut)來定義,並透過稱之為織入(weaving)的機制將稱為 advice 的剖面程式碼整合入功能模組中,
從而合成完整程式,滿足系統整體需求。這樣實現橫跨性關注的程式碼就可以集中封裝於適當的模組
中,避免掉程式碼糾結與重複的問題。
三年前,我們的國科會研究計畫(NSC 95-2221-E-004-004-MY2)以型態導向的方法,發展了一
套多型剖面的織入技術,並據以設計實作了一個實驗性的剖面導向函數語言 AspectFun。它的主要特
點是擴充傳統的 Hindley-Milner 型態系統,採用 advised types 以及內嵌的 advice predicates 來捕捉多型
剖面織入所需的資訊,我們依此發展的靜態編織技術可以充分支援多型剖面,達到結合剖面與函數語
言的優勢 [2, 3]。
此三年期計畫,我們以 AspectFun 語言與實作為基礎,進一步來探討剖面機制在函數式語言的程
式模組化與擴充性方面的一些課題,所要進行的探討涵蓋實作、語言設計與理論各個面向,主題則大
致上可分為四部份: (1) AspectFun 語言核心機制的擴充與實作,以奠定後續研究的基礎。(2)探討如何
以剖面處理函數語言型態擴充(type extension)的需求。(3)設計與實作具備狀態處理功能的剖面
(side-effecting aspects)來處理的狀態改變這個橫跨性關注,提高純粹函數程式的模組化,(4)定義
side-effecting aspects 的操作型與語義,驗證我們的織入(weaving)方法的正確性。
二、研究目的
本計劃的基礎是先前我們發展出來的剖面導向函數語言 AspectFun,期語法如下:
圖一: AspectFun 語言語法(精簡版)
2
但要真正應用起來,還有一些不方便之處。例如,在 advice 內僅能取得 advised 函數的引數,無法像
AspectJ 一樣,可以取得更多的關於 join point 的資訊,像是 advised 函數的名字與型態等的資訊(this join
point, tjp)。其次,AspectFun 語言目前僅提供一些基礎形態和常用的 tuples 和 lists 兩種結構型態,還
欠缺一個定義資料型態(data type)的核心功能,使得應用範例的撰寫上頗為侷限。所以我們新增此一
功能,這部份也是接下來兩項研究項目的必要元素。
接著,我們比較剖面與 Haskell 函數語言的 type class 在處理型態擴充需求議題的可行性,型態擴
充議題我們以著名的 Expression Problem[4] 作為代表(程式語言機制如何確保程式在擴充資料與運算
函數的過程中,不需要修改舊有的程式碼,並能確保程式的型態安全)。接著我們會探討 AspectFun
剖面如何以模組化方式來輔助實現泛型程式設計(generic programming)[5]。泛型程式設計是指函數接
收一額外的型態引數,且函數所執行的運算是依據此型態引數結構來進行的。型態引數是用來表示函
數所處理的引數或其回傳值型態為何,但型態引數在函數定義中可能是明確定義的或者隱含的。在此
研究中我們會展示以剖面實現的泛型程式設計更優於使用 type class。此外,本研究為使 AspectFun 可
以實現泛型程式設計方法,必須採用 safe typecast 設施,我們亦在 AspectFun 增加擴充了 Existential
types 機制與多型互遞迴函數的織入技術。
最後,AspectFun 語言跟 Haskell 語言[6]一樣,是一個純粹的函數式語言,並沒有提供 assignment
等的狀態處理(side-effecting)機制(.除了 print 外)。而在這樣的語言中,一個最基本常見的橫跨性關注
就是 side-effect via assignments。一般說來,使用純粹函數式語言時,所有牽涉到共用狀態(shared state)
的函數通通要改寫,將狀態當成一額外的參數,在彼此之間相互傳遞(threaded with a state parameter)。
近來 Haskell 引進 monad [7][8]的機制,可以將狀態處理封裝在 monad 之內,大幅簡化狀態處理的程
式撰寫,但是一旦使用 monad,程式所有相關部分都要改成 monadic 的寫法,因此也是一種橫跨式的
大幅變動。偏偏 aspect 常用來實現的 profiling 與 tracing 等工作,都是需要有狀態處理功能的,所以
即便我們可以將狀態處理部分限縮在 aspect 內,以 monad 實現模組化狀態處理的需求,但是原本是單
純的函數構成的程式本體,卻也必須跟著改寫成 monadic 方式,如此一來,大大減損了使用 aspect 模
組化的優勢。所以本計劃的一個重點就是要探討如何在 AspectFun 直接提供具備狀態處理功能的剖面
(side-effecting aspects),透過編譯技術將其與程式本體自動轉換成 monadic 方式,以提高純粹函數程式
的處理狀態的模組化程度。此外,這個機制還必須能在不影響 AspectFun 語言的 lazy semantics 下運作。
所以,我們定義 side-effecting aspects 的操作型與語義,驗證我們的織入(weaving)方法的正確性。
三、文獻探討
自從 1997 年 Kiczales et al. [1]提出 AOP 的程式設計方法後,許多的程式語言與軟體工程的學者
專家紛紛投入這方面的研究,彷彿循著當年 OOP 的發展軌跡一般,由 Programming, Design 漸漸進入
Analysis (OOA)。陸續我們看到各式 AO 程式語言的誕生,以及從整個軟體開發流程角度切入探討 AO
3
社群與每年固定的學術會議。至於在 AO 的理論基礎方面就像 OOP 一樣,起步稍晚,但目前也已經
有許多重要的論文發表,以下就其中與本計劃論文相關部分作一概況說明與評述。
關於 AO 程式語言機制的理論探討大多以 AspectJ 或其他 OO 語言為標的,試圖去界定 pointcuts,
advice 與 weaving 之間的語義。重要的結果有 Wand et al. [10]和 Masuhara et al. [11]所提出的 interpreter
式的動態語義,當程式執行中遇到了 join point 才開始尋找適當的 advice 予以 weaving 織入,重點在
於定義 pointcut expressions 與 advice 的意義;並沒有考慮 compile-time 靜態語義與型態資訊,也沒有
考慮 higher-order 函數。最近 Clifton and Leavens[12]針對 AspectJ 的 around advice 設計了一套相當完
整但也複雜的語意系統,包含了靜態語義與動態語義,不僅可以處理 around advice 所帶來的一些理論
上的困難,也可以清楚勾勒出 around advice 中的 proceed 對織入對象程式所可造成一些微妙的效果。
不過基本上,這個 MiniAMO 語言還是未考慮多型剖面。
在函數式語言方面,初期有些學者針對 AO 的語意與 higher-order 函數提出了不同的處理方式
[13][14],但仍然以動態語義為主,沒有考慮靜態語義與利用型態資訊來探討 aspect weaving。AO 在
靜態型別函數式語言的研究始於 Walker et al[15],他們提出了一個將 join point 視為 first-class value 的
形式演算系統,並從而設計了一簡化的 AO 函數式語言。但這個語言不支援多型剖面也無法處理
higher-order 函數。他們接者發表了新的可以支援多型剖面與 cflow-like 機制的 PolyAML 與 AML[16]
語 言 雛 形 , 其 中 用 到 了 極 為 複 雜 的 local type inference 來 推 導 多 元 型 態 。 不 過 他 們 在 處 理
non-polymorphic aspects 方面用了需要動態型態檢查的機制(type-case),所以無法在 compile-time 就完
成型態檢查與剖面織入。而我們的 AspectFun 語言在表達上雖不如 PolyAML 那樣豐富,但完全是使
用靜態型別推演與靜態方式完成剖面的織入。因為他們的基礎是具備 assignment 的 ML 語言,他們並
不需要處理狀態的需求,此外,也沒有進一步探討程式擴充性的議題。
有別於 PolyAML 從理論角度切入,東京大學 Masuhara et al. [17]則從多型剖面的實驗平台著手。
他們以 Object Caml 語言為基礎,仿 AspectJ 的機制在 Caml 上定義了相當完整的靜態與動態剖面機制,
並支援多型剖面。他們的目的在於透過此雛型語言累積多型剖面等方面的經驗,所以尚未發展此語言
的語意規則與型態系統。此外,Aspectual Caml 所採用的剖面織入方法是在程式編譯後,對程式的語
法樹(Abstract Syntax Tree)進行追蹤,尋找適合的剖面織入對象,雖然也會利用 advice 的型態資訊來避
免造成型態錯誤,但跟我們充分利用函數的型態資訊來進行織入動作相比,他們的方法還是只能算是
語法結構導向。因為像 higher-order 函數的呼叫往往是間接的,並不一定能從語法樹上找出來,再加
上多型帶來的複雜因素,他們只能在非多型 higher-order 函數的使用上正確織入 advice。其次,先前
提到的多型剖面帶來的 coherence 織入的問題,也無法由他們的語法結構導向的方法解決。比較之下,
AspectFun 語言可以充分利用諮詢性型態(advised types)達成,以靜態方式達成 coherent 的織入結果。
在純粹函數式語言內增加處理狀態機制的研究中,最主要的結果就是 Wadler 教授提出的 monad
機制[7][8]。早在 1997 年,AOP 剛剛提出來時,Meuter [18]就注意到 Aspect 與 Monad 之間的關連性,
4
最近 Hoper &Ostermann [19]發表了一篇論文,應用兩個典型的範例來比較 aspect 和 monad 在處理橫
跨性關注的表達能力,其中討論了兩者之間如何模擬另一方的功能。這和我們要進行的研究不同,我
們所計畫的 side-effecting aspect 是要直接在剖面內使用變數來撰寫狀態改變程式碼,然後再用所發展
的編譯技術,將程式轉譯成使用 monad 的 Haskell 程式碼,以達到封裝狀態處理於剖面內的程式模組
化目的。
在程式擴充性的研究方面,常常拿來當作標竿問比較各程式語言擴充機制的問題就是所謂的 the
Expression problem [4]。這個問題探討在程式的資料型態與運算函數同時要增加的情況下,是否可以
在不改變也不重複既有程式,而又能確保型態安全性的限制下完成。針對此問題,物件導向語言學者
提出來的解決方式是應用泛型(generics)和 visitor pattern 以及 virtual class[20][21]機制;函數式語言方
面則有 Caml 與 Haskell 的 polymorphic variant 機制[22][23]。最近物件導向與函數混合式語言 Scala 也
提出他們的解決方案[24]。在剖面導向語言方面,則還沒有比較深入的探討。不過,直得一提的是
Aspectual Caml 也注意到可能可以用 aspect 來處理程式擴充性的議題,因此設計了類似的 inter-type
declaration 的機制來實驗可能的解決之道。不過,他們並沒有在這方面進行嚴格的理論分析。比較接
近我們構想的研究是 Washbur&Weirich 所發表的,探討如何以 PolyAML 實現型態導向程式設計的論
文 [25]。
四、研究方法
分三方面概要說明如下,詳內內容請參考附錄中以發表之論文:
(一)在擴充 AspectFun 核心機制方面,我們增加了以下數項核心功能到 AspectFun 語言中:
(a)使用者自訂資料型態,範例:
data Tree a = Leaf a
| Node (Tree a) (Tree a)
(b)函數定義可以用 pattern matching 方式進行。範例:
sumTree (Leaf i) = i
sumTree (Node l r) = …
add (sumTree l) (sumTree r) = …
(c)增加關鍵字 tjp (this join point),供剖面取得 join point 的資訊。範例:
advice@tracing around {ANY} (arg) {
print “Function” + tjp + “called”; proceed arg }
這幾項功能中,都透過修改目前的 AspectFun 編譯器完成,此編譯器可在中研院資訊所發展的
自由軟體鑄造場(Open Foundry)下載。
5
(二)在型態擴充議題方面,我們首先在 AspectFun 內增加了 data+的機制,讓使用者可以擴充自訂資
料型態,並且在擴增新的資料型態內容時,就不必修改舊有資料定義的程式碼。以下列範例說明 :
data Shape = Circle Int in
drawShape :: Shape -> ( )
drawShape
(Circle
r)
=
drawCircle r in …
在本範例中,程式碼中定義了一資料型態 Shape 及函數 drawShape 來將不同的圖型 輸出至螢幕顯
示;一開始只定義了一種 Shape: Circle Int。當程式設計者想要對原有程式的 Shape 型態繼續擴充時,
可使用 data+擴增:
data+ Shape = …| Square IntInt
資料型態 Shape 現在擴充增加了 Square,原有的 drawShape 函數定義也必須跟著擴充,否則便會
造成程式在執行時發生錯誤。在 AspectFun 中可以藉由定義適當的 advice 操作 drawShape 函數來達到
擴充函數定義的目的 :
square@advice around {drawShape} (arg) =
caseargof
Square w h ->drawSquare w h
_
->proceedarg
in
…
當函數 drawShape 接收到 Square 圖型時,就會依此 advice 繪出 Square 型狀。
我們可以用這個 data+機制去處理 Expression problem 的型態擴充需求,但有一限制: 擴充的程式
碼必須跟原來的程式碼出現在同一源碼檔,目前尚無法個別編譯。所以,我們還提供另一種參照 open
datatypes 的 作 法 : 將 每 個 資 料 建 構 函 數 (data constructor) 提 升 為 獨 立 的 資 料 型 態 建 構 函 數 (type
constructor),但運用 advice 來擴充既有函數的定義。以 Expression 求值範例表述如下:
eval
::
a
->Int
eval
=
undefined
data Lit = Lit Int
data Plus a b = Plus a b
n@advice around {eval} ((Lit x)::Num) = unLit x
n1@advice
around
{eval}((Plus x y)::Plus a b) =
eval x + eval y
然後擴充運算式型態,增加 Minus 型態:
data Minus a b = Minus a b
n2@advice around {eval}
((Minus x y)::Minus a b) = eval x - eval y
6
中介泛型型態:
dataSpine a = Con (Constr a)
| forallb.ToSpine b =>
App
(Spine
(b
->
a))
b
dataConstr a = Descr a
這裡用到 existential types,所以我們也擴充 AspectFun,加了此一設施。
然後我們以 Washburn 等人提出的 泛型函數
strings
(從任意的結構型態中擷取其中的 string 包
成串列回傳)為例,來說明我們的方法,遇到的困難以及如何克服的作法。
strings :: a -> [String]
strings
x
=
strings‟ (toSpine x)
in strings‟:: Spine a -> [String]
strings‟ (Con c) = [ ]
strings‟ (App f x) =
strings‟ f ++ strings x in
n@advice around {strings}(arg :: String) = [arg] in …
在上述程式片段中,我們以 type-scoped advice n 處理當引數型態為 String 時的例外工作。在
AspectFun 中,我們期望每次 strings 被呼叫時皆會先觸發 advice n 判斷引數型態,但實際執行的情
況在 strings‟定義中呼叫 strings 時並沒有觸發 advice n,所以無法達到擷取字串的要求。
進一步分析後,發現問題的原因是由於 Spine a 定義中使用了 Existential type,但 AspectFun 是靜
態型別的語言,型態資訊只存在於編譯過程中,程式設計者無法直接對型態資訊進行操作。當程式中
使用 Existential type,型態推論系統為了確保型態安全只能推導出較抽象的型態資訊,因此在 strings‟
定義中呼叫 strings 時推導系統只能判斷出此時傳給 strings 的引數型態是某個未知型態 a 而非具體的
String 型態,導致 advice n 不會被觸發。
為了解決 Existential type 造成的型態資訊不足問題,我們採用的作法是將型態資訊保留至程式執
行期間,讓程式設計者可以在執行期間自行根據型態選擇運算工作,而非依賴編譯器藉著不足的型態
資訊決定。概念上,我們需要 safe typecast 機制[6],以利 advice n 的運作:
n@advice around {strings} (x) =
casecast x :: Maybe String of
Just
s
->
[s]
Nothing
->
proceed
x
要在靜態型別語言中增加safe cast設施,具體的作法是藉由資料型態定義來編碼表示帶有型態標籤的
值 :
7
資料型態 Typed 用來表示一數值與其型態的配對,TypeRep 用來表示此型態標籤。使用 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)
修改後的完整程式碼詳述於陳政宏的碩士論文[8],其中輔助泛型程式的部分已將發表於本計畫的
一篇期刊論文(附錄一)
。除必須採用 safe typecast 設施,我們亦在 AspectFun 增加提供多型互遞迴函
數的織入技術。
(三)在具備狀態處理功能的剖面機制方面,我們一方面擴充了 AspectFun 語法增加可宣告變數與執
行 assignment 的剖面,並發展將其編譯為 monads 方式的 Haskell 程式的轉換演算法。基本上我們要提
供使用者自定變數以及存取變數值的功能,語法如下:
Declarations
d
::= ··· | var id :: t [= e] | n@advice around {pc} (arg) = e
AspectDecl ad
::=
aspect
name where d
例如要追蹤階乘函數 fac(tail recursive)的執行過程,就可以用以下的 side-effecting 剖面來完成。 (追蹤
剖面 tracing aspect)
aspect Tracer where
var indent :: String
//儲存函數呼叫的追蹤訊息
//自動產生 getIndent, setIndent
tracer@aspect around{fac} (arg) = \acc ->
letind = getIndent
setIndent ("| " ++ ind);
putMsg $ind ++ "fact is call with " ++ show arg;
let result = proceed argaccin
8
putMsg $ ind ++ "`returns " ++ show result;
result
在編譯轉換方面,我們會將使用者自定變數當成 user state,連同輸出用的內部狀態一起用 state monad
封裝起來,並透過一些輔助函數,將這些處理狀態的剖面程式碼轉譯成 Haskell 的 monad 程式碼。以
上面的追蹤剖面為例,大致上會轉譯成下列程式碼;(轉譯後的追蹤剖面, monadic style)
type S a = State (UserState, InternalState) a //state monad
tracer proceed = return $ \arg -> return $ \acc ->
do ind <- getIndent
setIndent ("| " ++ ind)
putMsg $ ind ++ "fact is call with " ++ show arg
p<- proceed arg
result<- p acc
setIndentind
putMsg $ ind ++ "`returns " ++ show result
return
result
但還有引數(argument)求值的議題要設法解決。因為 AspectFun 跟 Haskell 一樣,都是採取惰性求值(lazy
evaluation)方式來計算引數的值;但是引進 monad 後,由於 monad 的特性會與惰性求值產生一些互動,
上面的轉譯後的程式的執行結果與預期會有不同,monad 的取值動作(bind)會改變引數的求值順序或重
複計算。針對此一問題,我們發展了一個 caching state monad 來保留惰性求值,附錄的 PEPM’09 論文
中詳述了這個解決方法。
接著我們進行對上述這個轉換演算法的正確性進行探討,其中一個難題就是這個問題示當我們要
轉換的對象是高階函數時,原有的轉換言算法就不能正確運作。針對此問題,我們擴增了一些轉換機
制,讓高階函數也能透過原本的架構順利運作,但也無可避免的增加了一些轉換後程式碼執行期的負
擔(overhead)。此外,我們也完成了這些機制的形式化分析,得出 AspectFun 擴充後的操作型語義,並
且能夠克服先前無法處理高階函數的限制,推導出正確性定理之證明,詳細的結果發表於 HOSC 國際
期刊(附錄二)
。
五、結果與討論
我們計畫的研究重點在探討如何以剖面(aspect)與編譯技術來模組化純粹函數語言內最基本的橫跨
性關注:狀態處理 (state manipulation via assignment)。我們的出發點是我們稍早所發展出來的剖面導
向函數語言 AspectFun,AspectFun 語言跟 Haskell 語言一樣,是一個純粹的函數式語言,並沒有提供
assignment 等的狀態處理(side-effecting)機制(.除了 print 外)。在這樣的語言中,一個最基本常見的橫跨
9
state)的函數通通要改寫,將狀態當成一額外的參數,在彼此之間相互傳遞(threaded with a state
parameter)。
近來 Haskell 引進 monad 的機制,可以將狀態處理封裝在 monad 之內,大幅簡化狀態處理的程式
撰寫,但是一旦使用 monad,程式所有相關部分都要改成 monadic 的寫法,因此也是一種橫跨式的大
幅變動。偏偏 aspect 常用來實現的 profiling 與 tracing 等工作,都是需要有狀態處理功能的,所以即便
我們可以將狀態處理部分限縮在 aspect 內,以 monad 實現模組化狀態處理的需求,但是原本是單純的
函數構成的程式本體,卻也必須跟著改寫成 monadic 方式,如此一來,大大減損了使用 aspect 模組化
的優勢。所以本計劃的一個重點就是要探討如何在 AspectFun 直接提供具備狀態處理功能的剖面
(side-effecting aspects),透過編譯技術將其與程式本體自動轉換成 monadic 方式,以提高純粹函數程式
的處理狀態的模組化程度。此外,這個機制還必須能在不影響 AspectFun 語言的 lazy semantics 下運作。
針對此一問題,我們發展了具備狀態處理功能的剖面 (side-effecting aspects)與 monadification 程式
轉換演算法。一方面使用者可以定義狀態變數,然後在剖面中使用,直接撰寫需要狀態處理的剖面。
另一方面,我們設計了一套程式轉換技術,將這些具狀態的剖面與基底程式(base program)自動轉成使
用 monad 的程式。我們的做法不僅可以處理一般程式,對於應用惰性求值(lazy evaluation)的函數式語
言也可適用。透過這樣的處理,使用者可以直接以剖面順利處理的狀態改變,提高純粹函數程式的模
組化。我們已經將這套機制與轉換技術實作於 AspectFun 語言中。我們的 AspectFun 編譯器已發布於中
研院的開放源碼平台 Open Foundry。此外,我們定義了 side-effecting aspects 的操作型與語義,驗證我
們的程式轉換與織入方法的正確性。
在以剖面處理函數語言型態擴充(type extension)的需求方面,我們探討如何以剖面處理函數語言型
態擴充(type extension)的需求,一方面是如何處理 Expression problem 的需求,另一方面是探討如何以
剖面的模組化方式來輔助實現泛型程式設計。過程中,我們也發展了一些擴充 AspectFun 的機制,大
幅提升了 AspectFun 的可用性。我們所提出的作法的主要限制是個別編譯(separate compilation)。理想
上,解決 Expression Problem 的最終目標是希望舊有程式碼可以不用重新編譯。由於 AspectFun 是個
靜態織入的語言,程式中定義的剖面必須在編譯過程中判斷是否會織入至函數中,當程式定義了新的
剖面且此剖面會影響到舊有定義的函數,在程式編譯的過程中需要知道舊有程式碼中函數的定義,編
譯器並且對 AspectFun 程式碼進行織入轉換,受限於 AspectFun 目前的型態推導系統與編譯系統限
制,目前 AspectFun 還未能支援個別編譯,基於 Expression Problem 及日漸龐大的應用程式系統,個
別編譯有助於程式碼的重用性,未來我們期望能發展出支援個別編譯的織入方法。
10
[1] G. Kiczales, J. Lamping, A. Menhdhekar, C. Maeda, C. Lopes, J.-M. Loingtier, and J. Irwin,
Aspect-oriented programming, in ECOOP '97 Object-Oriented Programming 11th European Conference,"
Finland (M. Aksit and S. Matsuoka, eds.), vol. 1241, pp. 220-242, New York, NY: Springer-Verlag, 1997.
[2] MengWang, Kung Chen, Siau-ChengKhoo, Type-DirectedWeaving of Aspects for
Higher-orderFunctionalLanguages, ACM SIGPLAN 2006 Workshop on Partial Evaluation and Program
Manipulation (PEPM '06) Charleston, South Carolina, January 9-10, 2006.
[3] Kung Chen, S C Weng, M Wang, S.C. Khoo, and C.H. Chen. “A Compilation Model for Aspect-Oriented
Polymorphically Typed Functional Languages”, International Symposium of Static Analysis (SAS 2007),
Lecture Notes in Computer Science 4634, pp. 34-51. < NSC-95-2221-E-004-004>
[4] P. Wadler, The expression problem. Posted on the Java Genericity mailing list (1998).
[5] Ralf Hinze and Andres Loh, Generic programming in 3D, Science of computer programming Vol. 74,
Issue 8 (June 2009).
[6] The Haskell Language Report,
http://www.haskell.org/onlinereport/
[7] P. Wadler, The essence of functional programming, 19'th Symposium on Principles of Programming
Languages, ACM Press, Albuquerque, January 1992.
[8] P.Wadler, Monads for functional programming, in J. Jeuring and E. Meijer, editors, Advanced Functional
Programming, Springer Verlag, LNCS 925, 1995.
[9] Early Aspects: Aspect-Oriented Requirement Engineering and Architecture Design, website at
http://www.early-aspects.net/
[10] M. Wand, G. Kiczales, and C. Dutchyn. A semantics for advice and dynamic join points in
aspect-oriented programming. Foundations of Aspect-Oriented Languages (FOAL), 2002, Extended version:
ACM TOPLAS, 26(5):890–910, September 2004.
[11] Hidehiko Masuhara,
GregorKiczales
: Modeling Crosscutting in Aspect-Oriented Mechanisms.
ECOOP
2003
: 2-28.
[12] C. Clifton and G. Leavens. Minimao: Investigating the semantics of proceed. In Proceedings of the
Foundations of Aspect-Oriented Languages, 2005.
[13] D. Tucker and S. Krishnamurthi. Pointcuts and advice in higher-order languages.In Proceedings of the
2nd International Conference on Aspect-Oriented Software Development, 2003.
[14] R. Jagadeesan, A. Jeffrey, and J. Riely. A calculus of untyped aspect-oriented programs. In Proceedings
of the 2003 European Conference on Object Oriented Programming, pages 54–73. Springer, 2003.
[15] Daniel S. Dantas and David Walker, Harmless advice, In Foundations of Object-Oriented Languages
(FOAL 05), March 2005.
[16] D. S. Dantas, D. Walker, G. Washburn, and S. Weirich.PolyAML: a polymorphic aspect-oriented
functional programmming language. In Proc. of ICFP’05. ACM Press, September 2005.
[17] H. Masuhara, H. Tatsuzawa, and A. Yonezawa. Aspectual Caml: an aspect-oriented functional language.
In Proc. of ICFP’05. ACM Press, September 2005.
[18] Wolfgang De Meuter, Monads as a theoretical foundation for AOP, Workshop on Aspect-Oriented
Programming at ECOOP, 1997.
11
[20] M. Torgersen, The expression problem revisited - four new solutions using generics. ECOOP’04.
[21] E. Ernst, The expression problem, Scandinavian style, ECOOP’04.
[22] Jacques Garrigue, Code Reuse Through Polymorphic Variant, In Workshop on Foundations of
Software Engineering, November 2000
[23] Koji Kagawa, Polymorphic variants in Haskell, Proceedings of the 2006 ACM SIGPLAN workshop on
Haskell.
[24] Matthias Zenger, Martin Odersky, Independently Extensible Solutions to the Expression Problem,
Workshop on Foundations of Object-Oriented Languages, January 2005.
[25] Geoffrey Washburn, SetphanieWeirich, “Good advice for type directed programming aspect-oriented
programming and extensible generic programming” proceedings of the 2006 ACM SIGPLAN workshop on
generic programming
本計劃相關著作
*Kung Chen, S.C. Weng, J.Y. Lin, M. Wang, and S.C. Khoo, (2011), Side-Effect Localization for Lazy,
Purely Functional Languages via Aspects, to appear in Higher-Order Logic and Symbolic Computation, 2012.
(published online on June 14, 2011)
*Kung Chen, S.C. Weng, M. Wang, S.C. Khoo, and C.H. Chen, (2010) “Type-Directed Weaving of Aspects
for Polymorphically Typed Functional Languages”, Science of Computer Programming 75, Issue 11, pages
1048-1076, Nov. 2010.
*Kung Chen, J.Y. Lin, S.C. Weng, S.C. Khoo, Designing Aspects for Side-Effect Localization, published in
Proceedings ACM SIGPLAN Symposium on Partial Evaluation and Program Manipulation (PEPM 2009),
Savannah, Georgia, USA, January 19-20, 2009.
* 林佳瑩,剖面導向函數語言之模組化狀態處理,
碩士論文,國立政治大學,2009.
12
附錄一
Kung Chen, S.C. Weng, M. Wang, S.C. Khoo, and C.H. Chen, (2010) “Type-Directed Weaving of Aspects for
Polymorphically Typed Functional Languages”, Science of Computer Programming 75, Issue 11, pages
1048-1076, Nov. 2010.
13
附錄二
Kung Chen, S.C. Weng, J.Y. Lin, M. Wang, and S.C. Khoo, (2011), Side-Effect Localization for Lazy, Purely
Functional Languages via Aspects, to appear in Higher-Order Logic and Symbolic Computation, 2012.
Contents lists available atScienceDirect
Science of Computer Programming
journal homepage:www.elsevier.com/locate/scico
Type-directed weaving of aspects for polymorphically typed
functional languages
Kung Chen
a, Shu-Chun Weng
b, Meng Wang
c, Siau-Cheng Khoo
d,∗, Chung-Hsin Chen
a aNational Chengchi University, TaiwanbNational Taiwan University, Taiwan cOxford University, United Kingdom dNational University of Singapore, Singapore
a r t i c l e i n f o
Article history:
Received 17 September 2008 Received in revised form 1 March 2010 Accepted 9 April 2010
Available online 20 May 2010 Keywords:
Aspect-oriented programming Type-scoped advice Static weaving
Polymorphically typed functional language
a b s t r a c t
Incorporating aspect-oriented paradigm to a polymorphically typed functional language enables the declaration of type-scoped advice, in which the effect of an aspect can be harnessed by introducing possibly polymorphic type constraints to the aspect. The amalgamation of aspect orientation and functional programming enables quick behavioral adaption of functions, clear separation of concerns and expressive type-directed programming. However, proper static weaving of aspects in polymorphic languages with a type-erasure semantics remains a challenge. In this paper, we describe a type-directed static weaving strategy, as well as its implementation, that supports static type inference and static weaving of programs written in an aspect-oriented polymorphically typed functional language,AspectFun. We show examples of type-scoped advice, identify the challenges faced with compile-time weaving in the presence of type-scoped advice, and demonstrate how various advanced aspect features can be handled by our techniques. Finally, we prove the correctness of the static weaving strategy with respect to the operational semantics ofAspectFun.
© 2010 Elsevier B.V. All rights reserved.
1. Introduction
Aspect-oriented programming (AOP) aims at modularizing concerns such as profiling and security that crosscut components of a software system [11]. In AOP, a program consists of many functional modules and some aspects that encapsulate the crosscutting concerns. An aspect provides two kinds of specification: pointcut, comprising a set of functions, designates when and where to crosscut other modules; and advice, which is a piece of code, that will be triggered for execution when the corresponding pointcut is reached during run time. The complete program behavior is derived by some novel ways of composing functional modules and aspects according to the specifications given within the aspects. Such a composing activity can be done at compile time or run time, and is referred to as weaving in AOP. Weaving results in the behavior of those functional modules impacted by aspects being modified accordingly.
While majority of the developments of AOP have been based on the object-oriented (OO) paradigm, there has been increasing awareness that the idea of AOP, if not the exact mechanism developed in the OO setting, is able to offer distinguished benefit to conventional functional languages in terms of modularity [23,22]. To start with, let us consider a simple example of sorting a list. Assuming we already have a function
sort :: [a] -> [a]
that implements the quicksort algorithm and picks the pivot from the head. For specific application domains, it is generally very useful if we can∗Corresponding author.
E-mail address:[email protected](S.-C. Khoo).
0167-6423/$ – see front matter©2010 Elsevier B.V. All rights reserved.
augment the algorithm with some domain knowledge in a modular fashion. For example, in an application of predominately nearly sorted lists, the following aspect provides a special case for already sorted lists. (Our aspect language employs a syntax very similar to that of Haskell. Detailed syntax will be presented in the following section.)
opt@advice around {sort} (arg) =
if isSorted arg then arg else proceed arg
This piece of code defines an aspect with the name
opt
, which designatessort
as the pointcut. Effectively, this aspect watches functionsort
and executes its advice body whensort
is called with an input that binds toarg
. Since quicksort calls itself internally, nearly sorted lists also benefit from this aspect by having more efficient recursive invocations. The special functionproceed
, which may be called inside the body of around advice, is bound to a function that represents ‘‘the rest of the computation at the advised function’’; specifically, it enables control to be reverted to the advised function, such assort
. An important difference between callingproceed
and the actual function, saysort
, is thatproceed
does not trigger the same advice again.The same predicate,
isSorted
, can be used to impose contracts that are separated modularly from the main functional concern.corr@advice around {sort} (arg) =
let res = proceed arg
in if isSorted res then res else error "Not Sorted!"
This contract aspect performs the computation first by calling
proceed
; it then takes over the returned result and checks for its sortedness. Note that theerror
function is a built-in function of Haskell whose type isString->a
.Function
sort
is polymorphic, which works uniformly on all input lists with comparable elements. From time to time, we may want to adapt this generic behavior for some specific (set of) types. For example, suppose later in the development, we add into the system some 32 bits binary numbers encoded as records for constant access to each digit. Pair-wise comparison on them is thus expected to be expensive. We can then switch to the more suitable radixsort algorithm.radix@advice around {sort} (arg::[Binary]) = radixSort arg
This aspect includes a type constraint
[Binary]
on its pointcut which limits the scope of its impact through type scoping on its argument; this is called a type-scoped advice. This means that execution ofradix
will be triggered only whensort
is invoked with an argument of such list elements.The advantage of using aspects is evident. Improvement to the existing program can be done modularly with easy deployment and retraction of aspects. Since multiple pieces of advice can be attached to the same point and executed in sequence, the aspects above can be picked and matched freely.
Though small, the sort example gives a glimpse of three important applications of AOP in functional programming that are summarized below.
1. Behavioral Adaptation: Aspect
opt
makes functionsort
behave differently for the special case of sorted list based on the inherited recursive structure of functionsort
.2. Separation of Concerns: Aspect
corr
allows contracts to be imposed on functionsort
separately from the functional component.3. Type-directed Programming: Aspect
radix
augments functionsort
with type specific behaviors.Given these distinct benefits, it is attractive to introduce AOP into functional languages. Indeed, notable proposals of AOP extensions have been made for ML [5,15]. However, proper static weaving of aspects in languages with type-erasure semantics such as Haskell remains a challenge. Specifically, it is difficult to determine statically the exact type context of an invocation of a polymorphic function in order to ensure proper weaving, at compile time, of aspects with type scopes. For example, consider the following program
sortcat l = concat ((map sort) l)
When compiling
sortcat
, it is not clear whether aspectradix
should be triggered as the element type of parameterl
is not known.Not only does this problem exist in the functional setting, it also exists in any AOP language with type-erasure semantics and parametric polymorphism. For example, as pointed out by Jagadeesan et al., correct static weaving of aspects are threatened by the introduction of generics in Java [8]. Jagadeesan et al. illustrate this concern through the following Java code:
class List<T extends Comparable<T>> {
T[] contents; ...
List<T> max(List<T> x) {
// general code for general types
} }
Programs π ::= dinπ |e
Declarations d ::= x=e|f x=e|n@advice around{pc}(arg) =e Arguments arg ::= x|x::t
Pointcuts pc ::= ppc|pc+cf |pc−cf Primitive PC’s ppc ::= f x|any|any\[f] |n Cflows cf ::= cflow(f) |cflow(f(_::t))
|cflowbelow(f) |cflowbelow(f(_::t)) Expressions e ::= c|x|proceed|λx.e|e e|letx = eine Types t ::= Int|Bool|a|t→t| [t]
Advice Predicates p ::= (f :t) Advised Types ρ ::= p.ρ |t Type Schemes σ ::= ∀¯a.ρ
Fig. 1. Syntax of the AspectFun language.
This class implements a list with a method
max
. When the input is a Boolean list, we may want to use bit operations for implementation efficiency. This can be attained via a type-scoped aspect.aspect BooleanMax {
List<Boolean> around(List<Boolean> x): args(x) &&
execution(List<Boolean> List<Boolean>.max(List<Boolean>)) {
// special code for boolean arguments
} }
However, for those invocations of
max
that occur inside another polymorphic method, we shall run into the same difficulty of static weaving as described above. Furthermore, due to the type-erasure semantics of Java, run-time type test of the list element type is not feasible. The solution presented in this paper, which works well in functional languages such as Haskell, can shed light on the possible improvement to the compilation of aspect-oriented programs written in other paradigms.In this paper, we present a type-directed aspect weaving scheme for polymorphically typed functional languages that can solve this problem with static weaving. We consolidate our past research in this field [21,20,2] and makes significant revisions and extensions to several dimensions of our research. Moreover, we illustrate our scheme with an experimental language,AspectFun, and provide the following:
1. A complete treatment of static and consistent weaving for the core features ofAspectFun, including type-scoped advice and nested advice (whose body is also advised).
2. A full formulation of the correctness of static weaving wrt the operational semantics ofAspectFunand its proof. 3. A complete implementation of our static weaving scheme which turns aspect-oriented functional programs into
executable Haskell code without aspects.1
The outline of the paper is as follows: Section2presents our experimental languageAspectFun, highlighting various aspect-oriented features our scheme supports through examples inAspectFun. Section3defines an operational semantics forAspectFun. In Section4, we describe our type inference system and the corresponding type-directed static weaving process. Next, we formulate the correctness of static weaving with respect to the semantics ofAspectFun. Finally, we discuss related work in Section6and conclude in Section7.Appendixprovides the detailed proof of the correctness of static weaving.
2. AspectFun: The aspect language
This section introduces the aspect-oriented functional language,AspectFun, for our investigation. We shall first describe the core features ofAspectFun, and outline the compilation process we employ to implement it. Then we shall present some example applications ofAspectFun.
2.1. Language features
Fig. 1presents the language syntax.2We writeo as an abbreviation for a sequence of objects o
¯
1, . . . ,
on(e.g. declarations, variables, etc.) and fv(
o)
as the set of free variables in o. We assume thato and o, when used together, denote unrelated¯
objects.1 The implementation is available athttp://of.openfoundry.org/projects/801/.
2 To simplify the presentation, we leave out type annotations, user-defined data types, if expressions, patterns and sequencings (;), but may make use of them in examples.
InAspectFun, top-level definitions include global variables and function definitions, as well as aspects. An aspect is an advice declaration which includes a piece of advice and its target pointcuts. The prefix part, n@, of an advice declaration simply names the advice under n. Pointcuts are denoted by
{pc}
(
arg)
, where pc stands for either a primitive pointcut, represented by ppc, or a composite pointcut. Pointcuts specify certain join points in the program in which advice is triggered when program execution reaches there. Here, we focus on join points at function invocations. Thus a primitive pointcut, ppc, specifies a function or advice name the invocations of which, either directly or indirectly via functional arguments, will be advised. Furthermore, the applicability of a piece of advice is bounded by its pointcut as well as its optional type scope, which is specified as part of the arg component, namely x::
t.Advice is a function-like expression that may be executed before, after, or around a join point. An around advice is executed in place of the indicated join point, allowing the advised pointcut to be replaced. A special keyword
proceed
may be used inside the body of around advice. It is bound to the function that represents ‘‘the rest of the computation’’ at the advised pointcut. As both before advice and after advice can be simulated by around advice that usesproceed
, we only need to consider around advice in this paper.A primitive pointcut can also be a catch-all keywordany. When used, the corresponding advice will be triggered whenever a named function is invoked. For example, the pointcutany
\[f
,
g]will select all named functions except f and g. Besides, since advice is also named, we allow advice to advise other advice. A sequence of pointcuts,{pc}
, indicates the union of all the sets of join points selected by the pci’s. The argument variable arg is bound to the actual argument of the named function call and it may contain a type scope.Note that, since our pointcuts are name-based, invocations of anonymous functions are not considered as join points, even whenanyis used. Besides, only global functions and advice are subject to advising. Although our weaving scheme can also handle local functions, we choose not to do so for it will make the base program not oblivious to the alpha conversion. On the other hand, because of this decision, we need to apply alpha renaming to local declarations beforehand so as to avoid name clashes.
In passing, we note two other features of primitive pointcuts. First, inAspectFun, advice is named and their names can appear in a pointcut. Thus we allow advice to be developed to advise other advice. We refer to such advice as second-order advice. Second, the function name in a primitive pointcut can be followed by an optional sequence of arguments to support advising on partially applied functions. Following the terminology used by Masuhara et al. [15], we refer to such pointcuts as curried pointcuts.
The composite pointcuts inAspectFunare those related to the control flow of a program. Specifically, we can write a pointcut which identifies a subset of invocations of a specific function based on whether they occur in the dynamic context of other functions. For example, the pointcut f
+
cflow(
g)
selects those invocations of f which are made when the function g is still executing (i.e. invoked but not returned yet). On the other hand, if the operator before thecflowdesignator is a minus sign (e.g. f−
cflow(
g)
), it means the opposite, namely only invocations of f which are not under the dynamic context of g will be selected.Following AspectJ, our aspect language also provides two kinds of pointcut designators for specifying control flow restrictions. The first one is expressed ascflow
(
f)
, and it captures all the join points in the control flow from the specific application to function f , including that specific f -application. The second one is expressed ascflowbelow(
f)
, and it captures all the join points in the control flow from the specific application to f , but excluding that specific f -application.Lastly, the expressions inAspectFunare pretty standard. As to the types, we introduce a conservative extension of the standard Hindley–Milner type schemes which includes the so-called advice predicates to form advised types,
ρ
. This construct is inspired by the predicated types [18] used in Haskell’s type classes. Advised type augments common type scheme with advice predicates,(
f:
t)
, to capture the need of static advice weaving based on type context. We shall explain them in detail in Section4.1.Before ending this part, we outline the implementation scheme we employ for compilingAspectFunprograms. The target language is Haskell [6]. The overall compilation process ofAspectFuncan be divided into ten steps, as outlined in
Fig. 2. Briefly, the process performs global analysis and optimization on a program and comprises the following five major components: (1) Syntactic processing and dependency analysis of anAspectFunprogram; (2) Static type inference to add type information to the abstract syntax tree; (3) Type-directed static weaving to convert aspects to functions and produce a piece of woven code; (4) Analysis and optimization of the woven code; (5) Translation of a woven program into a Haskell program. The first component is pretty standard. The second component performs a Hindley–Milner like type inference to reconstruct type information by treating advice as normal functions with proceed calls as recursive calls. Section4will present the third and the last component, which are the major results of this paper. We refer the readers to our earlier work [2] for details of the fourth component.
2.2. Examples
As outlined in the Introduction, there are three major applications of functional AOP, namely behavioral adaptation, separation of non-functional concerns, and type-directed programming, which distinguish it from traditional functional programming. In the sequel of the section, we illustrate these three points in more detail with examples. The complete
AspectFun program 1. Parsing and Dependency analysis Abstract syntax tree 2. De-sugaring Type environment for built-in functions Refined abstract syntax tree 3. HM-like Type inference Base type environment Explicit AspectFun 4. Static pointcut matching Function-advice association 5. Type Inference and Static weaving (Sec. 4) Expressions with chains 6. Guard insertion Not discussed in this paper Expressions with chains and guards 7. Cflow anlayses and Optimization 8. Monad Transformation Chain expressions 9. Chain expansion Sugared lambda calculus 10. Code generation Haskell program
Fig. 2. Compilation process of AspectFun .
2.2.1. Behavioral adaptation
AOP enables us to adapt and reuse existing code in a modular fashion. Let us consider an example of monadic evaluators [17] for the lambda calculus.
Example 1.
data Term =
Var String
|
Lam String Term
|
App Term Term
eval :: Term -> M Term
eval (Var n)
=
return (Var n)
eval (Lam n t)
=
return (Lam n t)
eval (App t1 t2)
=
do t1’ <- eval t1
t2’ <- eval t2
case t1’ of
Lam n t -> eval (subst (n,t2’) t)
t
-> return (App t t2’)
The evaluator,
eval
, reduces a lambda term using a monad,M
. The default evaluation strategy above is call-by-value. A definition of a call-by-name evaluator will be very similar and only differs in theApp
case. Instead of defining two separate functions that are largely overlapping, we can treat the above definition as a ‘template’ function and override it later by aspects.cbn@advice around {eval} (e) =
case e of (App t1 t2) ->
do t1’ <- eval t1
case t1’ of Lam n t -> eval (subst (n,t2) t)
t
-> return (App t t2)
Note that, inside the body of advice
cbn
, there are two calls to the function eval, which is being advised. We call such advice, whose body is also advised, nested advice.Aspects do in-place modification of the target functions, which makes the original definitions inaccessible in the same scope. In the above example, the semantics of
eval
is changed to call-by-name bycbn
; and we loses the original call-by-value evaluator. A way to avoid this problem is to alias the template function and advise the new name as follows.evalcbn = eval
cbn@advice around {eval+cflow(evalcbn)} (e) =
case e of (App t1 t2) ->
do t1’ <- eval t1
case t1’ of Lam n t -> eval (subst (n,t2) t)
t
-> return (App t t2)
_
-> proceed e
In this version, advice
cbn
employs a control-flow-based pointcut which only applies to recursive calls toeval
originated fromevalcbn
. We can still invoke the original call-by-value evaluator by callingeval
directly.Without aspects, the idiomatic way of achieving reuse of recursive pattern in functional programming is through higher-order combinators, such as fold. However, advanced planning is required; and programs must be written in a particular syntactic style.
2.2.2. Separation of non-functional concerns
The signature application of AOP is the modular tracing. There is no doubt that it is also useful in functional programming as well. Let us consider a simple example for illustration (Example 2).
Example 2.
--Tracing Aspects
n1@advice around {any} (arg) =
println "entering " ++ tjp;
proceed arg in
n2@advice around {f} (arg::[Char]) =
print " argument string ";
println arg;
proceed arg
in
--Base program
f x = x in
h x = f x in
(f 10, f "c", h "d")
// Execution trace
entering f
entering f
argument string: "c"
entering h
entering f
argument string: "d"
The code inExample 2defines two aspects named
n1
andn2
respectively; it also defines a main/base program consisting of declarations off
andh
and a main expression returning a triplet. The first aspectn1
employs the all-catching pointcut,any, to trace the execution of all functions in the main program. Inside the advice, a special run-time reflection
tjp
(standing for this join point) refers to the name of the advised function. Very often, for polymorphic or overloaded functions, we want to have more refined messages that reflect the type context of the executions. In aspectn2
, there is a type scope on the first argument. In addition to the generic trace produced by aspectn1
, aspectn2
prints out functionf
’s string (list of Char) inputs. The result of deploying the two aspects are shown to the right of the example code.32.2.3. Type-directed programming
We have seen examples that exhibit type specific behavior with type-scoped advice. This kind of type-directed programming is commonplace in functional programming and the modularity benefit brought in by AOP is highly desirable, as has been convincingly argued by Washburn and Weirich [23]. In this section, instead of showing more examples of purely static resolution of type-directed functions that readers are already accustomed to, we look at a functional idiom of Generic Programming [7] that generally requires some dynamic typing mechanisms and show how the extensibility of AOP plays a crucial role in constructing such an idiom [23,22].
Type-directed programming allows us specify a case for every data type. This is fine-grained, but not very general: we cannot write reusable definitions that explore structural similarities among types. Consequently, many boiler-plate codes are created [12]. Consider a function
strings
that extracts all the strings stored in a structure. With a nominal approach, we are required to define a case for every data type, which mainly specifies non-productive inductive traversals.In contrast, generic programming is about defining functions that work for all types but that also exhibit type specific behavior [7]. It exploits structural information of data types, and dispatches based on structural representations. For
3 Our compiler employs the composition of two Haskell functions,(unsafePerformIO . putStrln), to implement theprintlnoperation. Moreover, the sequencing construct, ‘‘;’’, is implemented in terms of theseqfacility of Haskell.