之前的一篇文章曾經說過我在準備一個面向組合子編程的ppt和demo。在公司內部進行試運行之后,今天終于開講了。
當然在這之間的幾個星期里面,我修改了ppt的內容,還有為demo添加了單元測試。所以我把
ppt的內容和demo的代碼也一起打包上來了。
ppt包含2003和2007兩種格式,demo使用的是Visual Studio2010的C#,當然是為了使用那個激動人心的“缺省參數”語法了。如果沒裝2010的話,那就趕緊裝一個哈,如果不想裝,那看看代碼就行了。
posted @
2010-09-01 20:42 陳梓瀚(vczh) 閱讀(5178) |
評論 (3) |
編輯 收藏
因為在開發
CMinus的過程中為了異常處理(最終沒有實現進CMinus),曾經學習了一下
怎么用匯編語言寫try-catch,因此這個CPU相關的處理方法就被我偷了哈,實現在了
NativeX的虛擬機里。
在NativeX里面,try-catch和throw非常簡單。throw你可以加一個值當異常數據,也可以不加(不會修改上次的異常數據,可以當rethrow用)。catch的話沒辦法跟C++一樣根據類型來判斷,因此我會給你一個異常數據的指針,你自己看著辦哈,因為NativeX跟C一樣沒有RTTI。因此throw就很簡單了,就是恢復棧頂和棧底指針之后跳轉到最近的異常處理程序里面去。try和catch就是用來創建和銷毀異常處理程序的。所有的異常處理程序構成了一個鏈表,這個鏈表被我記在了堆棧里面,而最近的異常處理節點的指針則被我放在了整個堆棧控件的最頂部,接在后面的是異常對象的數據。你每次throw的東西的尺寸可以不同,因此占用的“堆棧最頂部空間”也不同。當然如果你函數遞歸太深而導致棧頂覆蓋了異常對象的數據區域時,就會觸發“堆棧溢出”事件。在NativeX里面堆棧溢出代表你這程序已經廢了,因此這個是不能catch的,虛擬機返回給宿主程序一個信號然后就停止執行了。
我們來看一個簡單的例子,如何throw之后把異常對象的返回給函數,首先是代碼:
1 /*NativeX Code*/
2 unit nativex_program_generated;
3 function int32 main()
4 {
5 (result = 10s32);
6 try
7 Throw();
8 catch
9 (result = ( * cast<int32*>(exception)));
10 }
11
12 function void Throw()
13 throw 20s32;
main函數首先將函數返回值設置成10,然后調用throw函數。throw函數會把20給throw出來,然后main函數catch了,把結果返回。NativeX使用了關鍵字exception來表達異常對象的地址。當然你如果要throw各種不同的東西的話,你得自己做標記(親自實現RTTI)了。好了,我們看看產生的指令:
1 // unit nativex_program_generated;
2 0: stack_reserve 0
3 1: stack_reserve 0
4 2: ret 0
5 // function int32 main()
6 3: stack_reserve 0
7 // (result = 10s32);
8 4: push s32 10
9 5: resptr
10 6: write s32
11 // try
12 7: exception_handler_push 14
13 // Throw();
14 8: stack_reserve 1
15 9: stack_top 0
16 10: call 20 1
17 11: stack_reserve -1
18 // try
19 12: exception_handler_pop
20 13: jump 18 1
21 14: exception_handler_pop
22 // (result = ( * cast<int32*>(exception)));
23 15: exception_object_address
24 16: resptr
25 17: copymem 4
26 // function int32 main()
27 18: stack_reserve 0
28 19: ret 0
29 // function void Throw()
30 20: stack_reserve 0
31 // throw 20s32;
32 21: exception_object_reserve 4
33 22: push s32 20
34 23: exception_object_address
35 24: write s32
36 25: exception_raise
37 // function void Throw()
38 26: stack_reserve 0
39 27: ret 0
try首先會將catch之后的第一個指令給exception_handler_push了,在try的結尾當然要取消掉這個異常處理函數了,因此pop一下,然后jump到catch后面。當然catch的第一件事也是exception_handler_pop。exception_object_reserve在棧頂預留指定的空間來存放異常對象,exception_object_address則是獲得異常對象的地址,exception_raise就是跳轉到最近的異常處理函數了。raise不會把異常處理函數的記錄給pop掉,所以要靠catch自己去pop。
NativeX已經完成了,接下來就可以開始打造周邊工具了哇哈哈。將來的目標是將類似C#和Javascript的語言都編譯到NativeX上,然后為這三類語言寫很多語法分析器,然后他們就變成很多語言了。當然這些語言只是demo。
Vczh Library++的目的是提供實現編譯器的中間每一層的類庫,因此想干嘛就可以干嘛了哈。
posted @
2010-08-28 01:14 陳梓瀚(vczh) 閱讀(3495) |
評論 (9) |
編輯 收藏
摘要: 如上一篇文章所說,Vczh Library++3.0的NativeX語言實現了計劃的所有泛型語法。讓我們來看一個簡單的例子:我們為類型寫一個Equals函數。我們可以為普通類型,譬如int32寫一個Equals函數。我們有Vector<T>類型,只要T類型擁有一個Equals函數,那么Vector<T>顯然也可以有Equals函數。問題...
閱讀全文
posted @
2010-08-26 03:09 陳梓瀚(vczh) 閱讀(3107) |
評論 (3) |
編輯 收藏
這幾天寫了一個關于面向組合子編程的ppt。幾個月前跟某個dev lead借了他翻譯的中文版
Pattern Hatching,條件是要在公司里面開一場關于設計模式的講座。其實本來一個月前就要講了,不過中間出了點事情,所以等到這個月才開始。因此我挑選了面向組合子編程的這個主題,做了個demo和ppt。
面向組合子編程原本是函數式編程的內容,主要說的是既然我們可以用Composite模式(參見
這里和
這里)來做出像樹一樣的數據結構,那么我們做出組合起來跟樹一樣的行為(譬如Command模式,用類代表行為)不也可以嗎?這個做法當然是行得通的,只不過一般我們很難看到一個需求的時候,可以意識到可以用面向組合子編程來搞定這個東西。因此我在這個ppt里面就舉了這樣的一個例子,也就是老掉牙的Log系統了:
我們編譯器在編譯代碼的時候,會產生下面的文件:
buildchk.err -- 記錄著錯誤
buildchk.wrn -- 記錄著警告
buildchk.log -- 記錄所有詳細信息和時間戳
命令行窗口 -- 記錄摘要,當然錯誤和警告還是要輸出來的,只是內容可以簡要一點
然后我就用面像組合子來開發了一個小巧玲瓏的系統,最終通過一個聲明式編程的接口暴露出來,然后你還可以往里面添加新的功能。
當然系統還是要經得起修改的,因此我還舉了個例子,如果有了需求變更——
err和wrn要加錯誤/警告的序號
支持GUI了——跟VS的錯誤列表差不多
最后展示了面向組合子編程最強大的威力——只需要添加零件,所有已經存在的工具都可以立刻在這個零件上面使用了,因此只需要非常少的代碼就可以完成這個需求變更。
這里就
放上我的ppt了。明天還要先開一個內部講座看看別人有什么意見然后進一步修改,完了demo在放出來。這個demo當然是C#寫的了,有GUI,C++寫GUI多麻煩啊……
posted @
2010-08-16 09:10 陳梓瀚(vczh) 閱讀(4913) |
評論 (15) |
編輯 收藏
最近在忙一些其他的事情。因為工作的關系我稍微花了點時間研究了一下C#,因此就沒往博客上寫文章了。
Vczh Library++ 3.0的工作也暫停了半個月,下個星期就要開始恢復了。最近Codeplex服務器的URL修改了導致項目連接不上,后來還是修掉了,主要是得手動更改sln文件的內容,花了好久才知道怎么做。
目前的進度是實現了generic constraint的數據結構但是沒有加入語法分析和語義分析的內容。generic constraint比較簡單,就如同C#的那個where,我可以寫:
1 concept T : IEq
2 {
3 
4 }
5
6 concept T : ISort
7 where T : IEq
8 {
9 
10 }
11
12 generic<T>
13 where T : ISort
14 function void Sort(T* values, int count)
15 {
16 
17 }
這是很重要的,因為沒有了where,在Sort下面就沒辦法使用ISort和IEq里面定義的函數了。在NativeX可以成為一門真正可以使用的中間語言之前,還必須實現下面的功能:
1、 generic constraint
2、concept instance函數調用
3、異常處理
4、外部函數接口
5、調試器接口
6、裝載的時候檢查元數據引入表是否匹配了所有已經加載的assembly
剩下的事情也不多了,就慢慢做吧。做完之后就可以開始寫一些parser來驗證這個NativeX究竟行不行了,我可以將Python和Basic都修改成一個類似C的語言(可以處理指針,沒有垃圾收集,等),然后把它編譯成NativeX的語法樹,這樣就可以開發一個支持多語言的編程接口并測試它了。
posted @
2010-08-06 18:31 陳梓瀚(vczh) 閱讀(3170) |
評論 (12) |
編輯 收藏
摘要: 根據之前的文章的討論,Vczh Library++3.0泛型全局存儲是一個大型的映射。假設你有下面的代碼:
1 generic<T>2 structure StorageType3 {4 wchar* name;5 T ...
閱讀全文
posted @
2010-07-17 21:28 陳梓瀚(vczh) 閱讀(2965) |
評論 (2) |
編輯 收藏
第三篇草稿講了泛型concept的概念,這篇最終稿可以確定
Vczh Library++ 3.0的NativeX所要支持的泛型concept的比較精確的特性了。
泛型的concept的概念還是比較清晰的,這次我們便通過一個例子來講解他。假如在NativeX里面實現一個列表,顯然因為NativeX只有指針,所以寫起來大概就是:
1 generic<T>
2 structure List
3 {
4 T* items;
5 int count;
6 }
于是我們想寫一個函數判斷兩個List是否相等。因為
NativeX現在已經有模板函數了,所以我們很簡單的一個想法是,傳入兩個List<T>*,然后再傳入一個函數指針function bool(T,T),就可以寫出下面的函數了:
1 generic<T>
2 function bool ListEquals(List<T>* xs, List<T>* ys, function bool(T,T) comparer)
3 {
4 result=true;
5 if(xs->count!=yx->count)
6 {
7 result=false;
8 }
9 else
10 {
11 variable int current=xs->count-1;
12 while(current>=0)
13 {
14 if(!comparer(xs->items[current], yx->items[current]))
15 {
16 result=false;
17 exit;
18 }
19 current--;
20 }
21 }
22 }
這個做法咋一看好像沒什么問題,但是如果我們要比較List<List<int>>怎么辦呢?我們可以先寫一個function bool IntEquals(int a, int b);,然后再寫一個function bool IntListEquals(List<T> a, List<T> b);,這個函數里面用IntEquals加上ListEquals<int>來實現,最后將他傳進ListEquals<List<int>>。到了這里還好,如果我們還想比較List<List<double>>、List<List<char>>等等,我們就要被迫搞出很多函數了。而且最大的問題是,當我們寫好這么多東西以后,我們想實現一個Find函數來查找List里面的對象的話,面對List<List<int>>、List<List<double>>等等的東西,我們又得重新寫一次了……
這個問題在面向對象的語言里面都很容易做到,只需要在一個類里面實現operator==就可以了。那這兩種方法有什么區別呢?假設我們把C++里面的類去掉,那么我們會發現,ListEquals<T>的comparer參數是通過T自動找到的,而不是我們自己傳進去的。因此如何設計一套可以從類型找到某一組函數的機制,就因為我們把模板函數加入NativeX語言(基本上就是C語言)而變成了一個必須實現的功能了。在這里我準備引入concept這個概念。concept跟
C++0x的concept以及
haskell的type class的概念基本一致。現在就讓我們逐步在NativeX里面實現這套機制。
我們在這里面對的問題是給一些給定的類型(或泛型類型,譬如說List<T>)實現Equals函數,然后每一個Equals函數里面如果需要其他類型U的Equals函數,可以直接找到,而不需要我們自己傳進去。因此這個問題可以抽象為,某些類型具有可以被兩兩比較是否相等的能力。因此定義如下:
1 generic<T>
2 concept Eq
3 {
4 bool Equals(T a, T b);
5 }
當然這些函數不能直接生出來,因此我們對于想比較的每一個類型都需要給出相應的Equals函數。下面的代碼為int類型定義了Equals:
1 instance int : Eq
2 {
3 Equals = IntEquals;
4 }
5
6 generic<T>
7 function bool IntEquals(int a, int b)
8 {
9 result=a==b;
10 }
每比較一次數字就得調用一次函數,這個開銷還是比較大的。在NativeX的所有設施都做好之后,我會開始做指令級別的優化,然后看看要不要實現自動判斷并inline的功能。有了int : Eq之后,我們就可以為List<T>也寫一個Eq了。如果我們給出了List<T>的Equals的話,為了使用這個Equals,T也必須有相應的Equals函數,于是我們可以寫:
1 generic<T>
2 instance List : Eq
3 where T : Eq
4 {
5 Equals = ListEquals<T>;
6 }
最后就是實現ListEquals了,注意ListEquals函數內部是如何拿到類型T的Equals函數的:
1 generic<T>
2 where T : Eq
3 function bool ListEquals(List<T> xs, List<T> ys)
4 {
5 result=true;
6 if(xs->count!=ys->count)
7 {
8 result=false;
9 }
10 else
11 {
12 variable int current=xs->count-1;
13 while(current>=0)
14 {
15 if(!Eq<T>::Equals(xs->items[current], yx->items[current]))
16 {
17 result=false;
18 exit;
19 }
20 current--;
21 }
22 }
23 }
這里引入了一個新的語法:Eq<T>::Equals,用于自動搜索自己dll或者其他dll實現的這個函數。搜索會在虛擬機里面完成,編譯器只負責提供符號,并檢查類型。因此就大功告成了。
這個instance的設計基本上來源于Haskell的type class,其實跟C++0x的那個concept關系還是比較小,畢竟NativeX沒有類,C++有類,Haskell沒有類。當然其缺點是你不能在定義了Eq<List<T>>::Equals的同時,專門為Eq<List<bool>>::Equals定義一個特殊的版本,就如同C++的偏特化一樣,這個就過于復雜了。雖然偏特化在C++的用處非常大,而且也十分常用,但是在NativeX里面就因為NativeX的模板可以編譯成二進制而會因為找不到高性能的實現方法被砍掉。
posted @
2010-07-13 04:26 陳梓瀚(vczh) 閱讀(3010) |
評論 (8) |
編輯 收藏
摘要: 經過一個星期的奮斗,二進制模板函數終于實現了,當然這還是沒有generic concept的版本。現在NativeX已經支持跟C#一樣的模板函數了:可以被編譯進獨立的二進制文件,然后另外一個代碼引用該二進制文件,還能實例化新的模板函數。現在先來看debug log輸出的二進制結構。首先是被編譯的代碼。下面的代碼因為是直接從語法樹生成的,所以括號什么的會比較多,...
閱讀全文
posted @
2010-07-12 03:12 陳梓瀚(vczh) 閱讀(3113) |
評論 (8) |
編輯 收藏
似乎C++“過于復雜”已經成為了詬病,不過對于我個人來講我實在很難理解這個觀點。之前有個朋友說stream::operator<<很復雜,其實也就是幾個overloading。還有些人說傳參數的時候很復雜,這無非就是復制構造函數、析構函數和引用吧。雖然我個人覺得模板元編程其實才是C++里面最復雜的地方,但是鑒于模板元編程實際的用處不大,我想應該只有少數幾個人會使用它。但是這樣很多人還是C++復雜,那我就不知道究竟在指什么了。
所以大家對C++有什么想噴的就趕緊留言哈,我也好看看別人是怎么理解的,然后討論討論。
(不過從我自己的角度出發,我認為凡是編譯器不能檢查的東西(譬如可變參數,指針類型強制轉換),都遠比能檢查的東西(模板元編程)要復雜,因為人很容易犯錯,機器不會。)
posted @
2010-07-06 19:52 陳梓瀚(vczh) 閱讀(11489) |
評論 (68) |
編輯 收藏
這
幾年來屢屢被網友教育說不要造車輪,我覺得我有必要專門寫幾句話來闡述我的觀點。
1:公司的代碼,自然有規定,你造不了車輪。
2:自己外包賺錢的代碼,造了也只會浪費時間,這個隨便你。
3:自己寫的代碼。無論你開源也好,不開源也好,自己寫那些不能換錢的代碼無非就是因為你寫的爽嘛,那造車輪還能提高自己功力,為啥總是有人來說這樣不行呢?這又不是公司的代碼,也不是拿去完成別人外包給我項目的代碼,這個時候你還用別人的東西,完全是沒有意義的。
除非你所謂的學習就是學習如何使用別人的車輪。當然我自己的定義是,學習造車輪,不僅能知道很多你不造車輪不知道的東西,同時造完了,你看別人的車輪,瞬間就知道怎么用了。而且如果你想的話,你還能研究一下怎么比別人造得更好。什么?你相信自己無論如何這一輩子寫到死也比別人爛么?那就是另一回事了。
posted @
2010-07-01 01:14 陳梓瀚(vczh) 閱讀(5839) |
評論 (21) |
編輯 收藏