為了讓更高級的語言可以編譯到
Vczh Library++ 3.0上面的NativeX語言,原生的泛型支持是必須有的。泛型不僅僅是一堆代碼的填空題那么簡單,因為編譯之后的Assembly(dll)必須可以容納泛型聲明,而且其他的Assembly可以實例化包含在其他Assembly里面的泛型聲明。這是非常麻煩的(被.net搞定了,jvm則由于種種原因搞不定,大概是因為jvm對assembly version的支持太差導致的,你知道.net 2.0的東西是不能引用4.0的dll的……)。不過先拋開這個不講,雖然如何在Assembly里面實現泛型我已經心里有數了,但是這里還是從語義的層面上來考慮泛型的設計。
在討論之前還是要強調一下一個大前提:
NativeX基本上就是一個語法更加容易看懂的C語言而已,功能完全是等價的。于是我要在NativeX上加泛型,其實也就是等于在C上面加泛型。我們使用泛型完成的事情可以有很多,譬如說定義泛型的結構體,定義泛型的函數,還有泛型的存儲空間等等。首先讓我們討論泛型的結構體。最終的語法可能會跟現在不一樣,因為NativeX的使命是作為一棵語法樹出現的,所以做得太漂亮的價值其實不是很大。
一、泛型結構體 泛型的結構體還是比較容易理解的,舉個小例子:
1 generic<type T>
2 structure Vector
3 {
4 T x;
5 T y;
6 }
這樣子我們就創建了一個泛型的結構體。任何熟悉C++或C#的人都知道這是什么意思,我就不做解釋了。使用的時候跟我們的習慣是一樣的:
1 Vector<double> v;
2 v.x = 1.0;
3 v.y = 2.0;
于是我們創建了一個泛型的變量,然后修改了它的成員。
二、泛型全局存儲空間 其實泛型的全局存儲空間基本上等于編譯器替你做好的一個key為類型的大字典。有些時候我們需要給類型加上一些附加的數據,而且是按需增長的。這就代表在編譯的時候提供泛型全局存儲空間的Assembly并不知道將來有多少個key要提供支持,所以創建它們的工作應該是虛擬機在鏈接一個想使用其他Assembly提供的全局空間的新Assembly的時候創建的。這里帶來了一個問題是不同的Assembly使用相同的類型可以訪問相同的全局存儲空間,這里先不討論具體實施的手段。
語法上可能比較混淆:
1 generic<type T>
2 structure TypeStorage
3 {
4 wchar* name;
5 function T() builderFunction;//這是函數指針
6 }
7
8 generic<type T>
9 TypeStorage<T> typeStorage;
在一個變量上面加泛型可能會有點奇怪,不過這里的定義還是很明確的。typeStorage是全局變量的泛型,因此typeStorage<int>、typeStorage<double>甚至typeStorage<Vector<Vector<wchar*>>>等等(啊,>>問題)是代表不同的全局變量。不同的Assembly訪問的typeStorage<int>都是相同的全局變量。
三、泛型函數 泛型函數我們也都很熟悉了。一個簡單的例子可以是:
1 generic<type T>
2 T Copy(T* pt)
3 {
4 result = *pt;
5 }
需要指出的是,NativeX并沒有打算要支持泛型結構、全局存儲和函數的特化或偏特化。因此我們會很驚訝的發現這樣的話泛型函數唯一能做的就是復制東西了,因為它調用不了其他的非泛型函數(跟C++不一樣,NativeX的泛型函數更接近于C#:編譯的時候進行完全的語義分析)。雖然泛型函數可以調用其他的泛型函數,但是最終也只能做復制。因此我們要引入一個新的(其實是舊的)概念,才可以避免我們為了提供各種操作在泛型函數的參數上傳入一大堆的函數指針:“概念”。
四、泛型concept 泛型結構體和全局存儲僅僅用來保存數據,所以泛型concept只能在泛型函數上面使用。這個concept跟C++原本打算支持的concept還是很接近的,只是NativeX沒有class,因此只好做一點小修改。
泛型concept主要是為了指定一套操作的接口,然后讓編譯器可以完成比調用函數指針更加高效的代碼。偷偷告訴大家,把他叫concept只是因為NativeX跟C很像,但其實這個概念是從Haskell來的……我們還是來看看concept怎么寫吧。
1 generic<type T>
2 concept Addable
3 {
4 operation T add(T a, T b);
5 operation T sub(T a, T b);
6 constant T zero;
7 }
8
9 generic<type T>
10 concept Multible : Addable<T>
11 {
12 operation T mul(T a, T b);
13 operation T div(T a, T b);
14 constant T one;
15 }
這里定義了加法和乘法的兩個concept。我們可以看出concept是可以繼承的,其實也是可以多重繼承的。concept里面可以放操作(operation),也可以放常數(constant)。這里的常數跟全局存儲的機制不同,全局存儲可以自動為新類型產生可讀寫的空間,而concept的常數不僅是只讀的,而且還不可自動產生空間。之前考慮到的一個問題就是,我們可能需要把外界提供的某個concept的operation的函數指針提取出來,有這種需要的operation可以把這個關鍵字替換成function,這樣在實例化concept的時候,那個標記了function的操作就只能綁定一個函數而不是一個表達式了。我們可以嘗試為int創建一個Multible的concept:
1 concept instance IntMultible : Multible<int>
2 {
3 operation T add(T a, T b) = a+b;
4 operation T sub(T a, T b) = a-b;
5 operation T mul(T a, T b) = a*b;
6 operation T div(T a, T b) = a/b;
7 constant T zero = 0;
8 constant T one = 1;
9 }
于是我們可以寫一個函數計算a+b*c:
1 generic<type T, concept Multible<T> multible>
2 function T AddMul(T a, T b, T c)
3 {
4 return multible.add(a, multible.mul(b, c));
5 }
然后調用它:
1 int r = AddMul<int, IntMultible>(3, 4, 5);
五、另一種concept instance 雖然我們不允許泛型的結構體、全局存儲和函數進行特化,但是因為特化實在是一個好東西。上面的concept instance是沒有彈性的,因為你不可能通過一個concept instance拿到另外一個concept instance。考慮一下delphi的帶引用計數的嵌套數組,如果我們想讓delphi可以編譯到NativeX上,則勢必要支持那種東西。主要的困難在于delphi支持的帶有引用計數的數組和字符串,因此在對array of array of string進行釋放的時候,我們首先要拿到array of array of string的concept instance,其次在釋放函數里面要拿到array of string的concept instance,最后還要拿到string的concept instance。這個用上面所提出來的方法是做不了的。因此我們引進了一種新的concept instance:叫concept series。這個跟haskell的東西又更接近了一步了,因為haskell的concept instance其實是匿名但是可特化的……
于是現在讓我們來實現Array和String,并寫幾個類型的Increase和Decrease的函數(函數體一部分會被忽略因為這里只是為了展示concept):
1 structure String
2 {
3 int reference;
4 wchar* content;
5 }
6
7 generic<type T>
8 structure Array
9 {
10 int reference;
11 int length;
12 T* items;
13 }
我們從這里可以看出,string跟array的區別就是在于長度上面,string有0結尾而array只能通過記錄一個長度來實現。現在我們來寫一個用于構造缺省數值、增加引用計數和減少引用計數的concept series:
1 generic<type T>
2 concept Referable
3 {
4 operation T GetDefault();
5 operation void Increase(T* t);
6 operation void Decrease(T* t);
7 }
8
9 generic<type T>
10 concept series DelphiTypeReferable : Referable<T>
11 {
12 }
concept series其實就是專門用來特化的concept instance。但是為了防止不同的Assembly特化出同一個concept series所帶來的麻煩,我可能會規定允許特化concept series的地方,要么是在聲明該concept series的Assembly,要么是聲明涉及的類型的Assembly。因為我的Assembly不允許循環引用,因此對于同一個concept series C<T,U>來講,就算T和U分別在不同的Assembly出現,那么也只能有一個有權限特化出它。下面來看特化具體要怎么做。首先我們特化一個簡單的,string的DelphiTypeReferable:
1 concept series DelphiTypeReferable<String>
2 {
3 operation GetDefault = StringGetDefault;
4 operation Increase = StringIncrease;
5 operation Decrease = StringDecrease;
6 }
StringGetDefault、StringIncrease和StringDecrease都是一些普通的函數,內容很簡單,不用寫出來。現在讓我們來看看Array應該怎么做:
1 generic<type T>
2 concept series DelphiTypeReferable<Array<T>>
3 {
4 operation Array<T> GetDefault() = ArrayGetDefault<T>;
5 operation Increase = ArrayIncrease<T>;
6 operation Decrease = ArrayDecrease<T>;
7 }
看起來好像沒什么特別,不過只要想一想ArrayDecrease的實現就知道了,現在我們需要在ArrayDecrease里面訪問到未知類型T的DelphiTypeReferable<T>這個concept instance。因為當自己要被干掉的時候,得將引用到的所有對象的引用計數都減少1:
1 generic<type T>
2 function void ArrayDecrease(Array<T>* array)
3 {
4 if(array->reference<=0)exit;
5 if(--array->reference==0)
6 {
7 for int i = 0
8 when i < array->length
9 with i--
10 do DelphiTypeReferable<T>.Decrease(&array->items[i]);
11 free(array->items);
12 array->length=-1;
13 array->items=null;
14 }
15 }
這樣一大堆concept series的特化組合在一起就成為會根據類型的變化而采取不同行為的concept instance了。于是我們還剩下最后的一個問題,那么其他類型的DelphiTypeReferable應該怎么寫呢?其實只需要玩一個小技巧就行了,不過在這里將會看到NativeX支持泛型的最后一個功能:
1 generic<type T>
2 concept series DelphiTypeReferable<T>
3 {
4 operation GetDefault = GenericGetDefault<T>;
5 operation Increase = null;
6 operation Decrease = null;
7 }
8
9 generic<type T>
10 T GenericGetDefault()
11 {
12 }
返回null的operation可以賦值成null以表示不需要執行任何東西。如果你將一個有副作用的表達式傳進去當參數的話,副作用會保證被執行。
關于語義上的泛型就講到這里了。
posted on 2010-06-12 23:58
陳梓瀚(vczh) 閱讀(2533)
評論(2) 編輯 收藏 引用 所屬分類:
VL++3.0開發紀事