Cool語言是一個面向對象的語言。雖然比較小型,但也包含了許多現代語言的特性,如對象、強類型和自動化內存管理。如果你之前熟悉c++、java等面向對象語言,那么會很容易上手。
學一門新語言,最急迫想知道的就是,如何編寫“hello world”程序,下面就來嘗個鮮。
class Main inherits IO {
main(): SELF_TYPE {
out_string("Hello, World.\n")
};
};
打眼一看,外形很像java,其實意思也很相近了。下面我來細細詳談。全文大部分翻譯cool語言手冊。
1,類結構 classes
一個Cool程序是由許多的類組成,每個類的定義必須在一個源文件里。但是多個類可以定義在一個源文件中。類的形式:
class <type> [ inherits <type> ] {
<feature_list>
};
<type>代表類名,[]里的內容是可選項。{}內是feature集合。所有類名都是全局可見的。類名是以大寫字母開頭的字符串。類不能重復定義(不能有兩個類名字相同)。
“hello world”例子,就只有一個類,名字是Main。
2,features
feature也就是我們說的函數和屬性的統稱。一個類是由數個features組成。
2.1 函數 methods
函數結構如下:
<id> ( <id> : <type> , ... , <id> : <type>) : <type> { <expr> }
<id>是標示符,也就是名字。<type>是類型。<expr>是表達式。()里面是函數的形參定義。例如:
add ( a : Int, b : Int ) : Int {
a + b
}
add是函數名,a和b是參數名字,":"后跟着的是參數的類型。") :"后跟着的是返回類型。{}里面就是函數內容,也就是表達式組成。表達式后面會講到。
類函數是外部可見的,也就是外部可以調用類的任何函數。
函數參數可以有1個或更多,也可以沒有,並且參數的名字不能相同。函數的返回值必須明確給出。如果參數的名字和類屬性名字相同,那么就會隱藏掉類屬性。也就是說在這個函數里,參數和類屬性名字相同,那么同名用的都是參數。
2.2 屬性 attributes
屬性結構如下:
<id> : <type> [ <- <expr> ];
<id>是屬性名,<type>是屬性類型。[]是可選的,屬性的初始化。也就是把<expr>表達式的值賦給<id>。例如:
class A {
x : Int <- 1;
}
類A有一個屬性為Int型的x,初始化為1。
初始化操作是在新建立一個類的對象的時候進行。在一個類內,屬性初始化的順序就是定義的順序。類屬性是局部變量,是類私有的。外部不能直接訪問類的屬性,只能通過函數去修改值。這也體現了現代OO語言的封裝性。
所有的屬性,如果沒有指定初始化,那么默認初始為void。void類似於c語言里面的NULL,或者java里的null。不過在Cool語言里沒有void的定義。唯一一個創建void值得方法就是在某個類里建立一個變量,除了Int,String,Bool類,並讓這個void初始化,或者保存while循環的結果。有一個表達式isvoid expr,用來判斷expr的值是不是void(會在之后表達式里講到)。
基礎類Int,Bool和String的屬性定義和初始化是特殊的,會在后面基礎類里講到。
3,繼承 inherits
繼承的結構如下:
class C inherits P { ... }
類C是類P的子類,類P是類C的父類。
類C除了自己定義的features,還繼承了所有父類P的所有features。如果子類父類定義了相同的函數,那么子類函數有更高的優先級。子類父類不定定義相同名字的屬性。
有一個特殊的類Object(后面基礎類會講到)。如果一個類沒有繼承任何類,那么會默認的繼承類Object。子類最多只能繼承一個父類,這也叫做“單繼承”。根據類的父子關系,可以定義一個集成體系圖。這個圖不能包含環。例如:類C繼承類P,類P就不能繼承類C。如果類C繼承類P,那么類P必須在程序中有定義。因為是單繼承,所以形成的圖是一個樹形結構,根節點就是類Object。
新對象建立,所有的基類的屬性也都要初始化操作,基類優先,逐步向外。
為了確保類型安全,繼承的函數有一些限制。規則很簡單:如果子類C從父類P繼承了函數f,那么類C必須覆蓋類P中f的定義,參數和返回類型必須保持一致。
class P {
f(): Int { 1 };
};
class C inherits P {
f(): String { "1" };
};
如果p是動態類型類P的對象,那么p.f() + 1的值就是2。但是如果p賦值為類C的對象,那么結果為string + Int,也就會出現問題。也就是如果子類隨意的覆蓋父類的函數,那么就不是對父類函數行為的擴展,繼承也就沒有作用。
4,類型 types
在Cool語言里,每一個類名也是一個類型。SELF_TYPE類型用在比較特殊的場合。
類型聲明通過x : C的方式。x是變量,C是類型。在使用一個變量前,這個變量必須聲明過。
在Cool里有一個基本規則。如果類C繼承類P,那么用到P的值地方,也可以用C的值代替。
如果類C的值可以代替類P的值,那么我們說C從屬於P,或者表示為C《 P(可以認為類C是繼承樹的位置更低)。就像上面所說的那樣,從屬關系根據的是繼承樹。
定義,A,C和P都是類型:
A 《 A
如果 C繼承P,那么C《 P
如果A《 C,並且C 《 P,那么A 《 P
因為Object是繼承樹的根節點,所有對於所有類型A,都有A 《 Object
4.1 SELF_TYPE
SELF_TYPE就是self變量的類型。在繼承的時候很好用,尤其是當避免寫類的具體類型。舉個例子:
class Silly {
copy() : SELF_TYPE { self };
};
class Sally inherits Silly { };
class Main {
x : Sally <- (new Sally).copy();
main() : Sally { x };
};
Silly的copy函數返回值的SELF_TYPE,那就意味着返回的是類調用者的類型。所以子類Sally調用copy函數,返回的也是Sally,而不是Silly。
我們可以注意到,SELF_TYPE類型並不是固定的,是根據相關類哪里出現。
還有一個用法,就是: new SELF_TYPE,可以作為函數的返回類型,或者let變量的類型聲明,或者屬性的聲明。除此之外,其它的用法是錯誤的。
4.2 類型檢查 Type Checking
Cool的類型檢查系統保證在編譯期通過的程序,不會在運行時候出現類型錯誤。程序員需要提供變量的類型,然后類型檢查器就會據此在程序用到的地方來推斷類型。
在編譯期,類型檢查器給變量定義類型,我們叫它表達式的靜態類型;在運行期一個表達式的計算的類型值,稱為動態類型。
有這種區別是因為,在編譯器,類型檢查器沒有在運行期時的動態數據,所以一般區別這兩種類型。然而我們關心的是靜態類型,可以保證程序運行時正確性。
5,表達式 expressions
5.1 常量 Constants
最簡單的表達式就是常量。布爾常量是true或false。整型常量是無符號的字符串,如:0,123,007. 字符串常量是被兩個雙引號包圍的字符序列,例如:"this is a string." 字符串之多只能有1024個字符長度。
這三個常量分別對應三個基礎類:Bool,Int,String。
5.2 標示符 Identifiers
局部變量的名字,函數的參數名字,self,和類的屬性都是標示符。self標示符可以被調用,但是不能給self賦值,或者在let、case或者函數參數里綁定它。也不允許把類屬性命名為self。
局部變量和函數參數都有詞法控件約束。類屬性的作用域在那個類或繼承的類里,盡管它可能被同名局部變量鎖隱藏。綁定的標示符作用在聲明的最內層空間。一個例外就是self,永遠表示的每個類。
5.3 分配 Assignment
形式結構如下:
<id> <- <expr>
expr表達式的靜態類型必須從屬於id的聲明類型。分配表達式的值就是expr的值,分配表達式的靜態類型就是expr的靜態類型。
5.4 函數調用 Dispatch
在Cool里,有三種形式的函數調用。之間的區別取決於如何選擇調用函數。最常見的形式是:
<expr>.<id>(<expr>, ... ,<expr>)
考慮一個一般的形式 e0.f(e1, ... ,en). 要計算整個表達式的值,先要計算參數的值。先從e1開始,從左到右直到en計算出值。下一步計算e0的值,查看e0屬於哪個類C(e0值如果是void,那么就會產生一個運行時錯誤)。最后,調用類C的函數f,並且e0的值綁定到函數f的self變量,實參綁定到形參里。Dispatch表達式的值,就是函數的返回值。
函數調用的類型檢查包括很多步。假設e0的靜態類型是A。那么A必須有一個函數f,這個f的參數和調用的參數數量必須相同,並且每個參數對應的靜態類型也必須符合。
如果函數返回類型B,並且B是一個類名。那么Dispatch表達式的靜態類型就是B。相反,如果函數f返回類型為SELF_TYPE,那么Dispatch的靜態類型就是A。因為函數f里的self變量綁定的是類A,返回SELF_TYPE類型,所以我們推斷返回類型的結果就是類A。
其它的兩種調用形式:
<id>(<expr>, ... ,<expr>)
<expr>@<type>.id(<expr>, ... ,<expr>)
前一種形式是 self.<id>(<expr>, ... ,<expr>)的簡寫形式。
后一種形式提供了一個方法,用來調用父類的函數。如果子類覆蓋了父類的函數,那么子類里只能調用子類定義的函數,父類函數因為隱藏掉了。這種形式指定了類型,所以就可以調用繼承關系任意類的函數。例如:e @B.f()調用類B的函數f。但是e的類型必須從屬於類B,也就是說必須是類B或B的子類。
5.5 條件 Conditionals
條件的形式如下:
if <expr> then <expr> else <expr> fi
一個標准的條件語法。先判斷第一個expr的值,如果是true,那么就計算then后expr的值;如果是false,那么就計算else后expr的值,fi表示條件語句結束。條件語句的值,就是所屬分支的值。
第一個expr的靜態類型必須是類型Bool,各分支沒有限制。至於條件表達式的類型,我們先定義一個符號“U",讓A,B,D為出了SELF_TYPE的任意一個類型。那么有:
A U B = C (C類型是A和B共同從屬的最小類型,也就是A、B相同的最小父類,小的意思是子類比父類小)
A U A = A
A U B = B U A
SELF_TYPE U A = D U A (假如SELF_TYPE指定類型D)
T代表為真的表達式的靜態類型,F代表為假的表達式的靜態類型。那么整個條件表達式的靜態類型為T U F。
5.6 循環 Loops
循環的形式如下:
while <expr> loop <expr> pool
先計算第一個expr的值,如果類型是true,那么計算第二個expr值,然后回到第一個expr計算;如果false,那么整個循環表達式返回void。第一個expr的靜態類型必須是Bool。循環表達式的靜態類型是類Object。
5.7 代碼塊 Blocks
代碼塊的形式如下:
{ <expr>; .. <expr>;}
表達式的計算順序是從左到右。每一個代碼塊必須至少有一個表達式;代碼塊的值就是最后一個表達式的值。代碼塊的靜態類型是最后一個表達式的靜態類型。
在Cool里,很容易把";”號用錯。分號是表達式的終結符,不是表達式的分隔符。詳細例子以后遇見了會介紹。
5.8 Let 表達式
let表達式形式如下:
let <id1> : <type1> [ <- <expr1> ], ... , <idn> : <typen> [ <- <exprn> ] in <expr>
初始化的表達式如<expr1>是可選擇的。如果有,先計算<expr1>的值,綁定到<id1>;然后在計算<expr2>,綁定到<id2>。如此順序直到<idn>。接下來進行let的主體,in后面的<expr>的計算。let的值也就是主體表達式的值。
<id1>到<idn>只有在let的主體里才是可見。並且當m > k對於<idm>,<idk>是可見的。
如果let定義的標示符名字重復,那么前面定義的會被隱藏掉。每個let必須至少聲明一個標示符。
每個初始化表達式的類型必須從屬於標示符聲明的類型。主體表達式的類型就是let的類型。
5.9 分支 Case
case形式如下:
case <expr0> of
<id1> : <type1> => <expr1>;
. . .
<idn> : <typen> => <exprn>;
esac
case提供了運行時的檢測。首先,計算<expr0>的動態類型為C(如果類型為void,那么會報運行時錯誤)。下一步,找出符合條件的分支。當C 《 <typek>,那么就選擇這個類型的分支。<idk>綁定<expr0>的值,然后計算<exprk>。如果沒有一個分支符合,那么產生一個運行時錯誤。每個case必須至少包含一個分支。
設Ti為<expri>的靜態類型,那么T1 U T2 U ... U Tn就是case的靜態類型。分支里聲明的<idk>,作用域在<exprk>里,並且隱藏了外部同名的標示符。
case沒有特殊的結構代表“default”或“otherwise”的分支。有一個相同的效果是包含分支:
x : Object => ...
因為每個類型都有 《 Object。
case提供程序員一個進行運行時類型檢測的方法,之前的都是類型檢測器推斷的保守的靜態類型。一個典型的應用場合:程序員寫一個表達式e,並檢測e是否有靜態類型P。然而事實上這個程序員可能知道e的動態類型總是C(C 《 P)。如下寫法可以捕獲這個信息:
case e of x : C => ...
在這個分支里,x綁定了更具體的靜態類型C的e表達式的值。
5.10 New 表達式
new的形式如下:
new <type>
根據<type>申請一個新的類。如果類型是SELF_TYPE,那么會根據當前空間self綁定的對象類型來建立。new的靜態類型為<type>。
5.11 Isvoid
isvoid的形式如下:
isvoid expr
如果expr值為true,那么isvoid為true;如果expr值為not void,那么isvoid為false。
6,基礎類 basic classes
Object
Obejct類是繼承樹的根節點類。定義了以下函數:
abort() : Object
type_name() : String
copy() : SELF_TYPE
abort()函數會終止程序,並攜帶錯誤信息。type_name函數返回類的名字。copy函數是對自身的一個淺拷貝(淺拷貝就是拷貝自身,不進行指針指向的內存拷貝)。
IO
提供了一些簡單的輸入輸出操作函數:
out_stirng( x : String ) : SELF_TYPE
out_int( x : Int) : SELF_TYPE
in_string() : String
in_int() : Int
out_string和out_int函數打印參數並返回self。in_string函數從標准輸入讀入一個字符串,直到有一個換行符。in_int函數讀入一個整型,直到一個空格。任何在整型之后的字符,直到並包括下個換行符,那么這個整型會被丟掉。
一個類可以繼承IO來用IO的函數。不能重新定義IO類。
Int
Int類是提供整型。這個類沒有函數。默認初始化時0(不是void)。不能繼承或重定義Int。
String
String類提供字符串。定義了以下函數:
length() : Int
concat( s : String) : String
substr( i : Int, l : Int) : String
length函數返回self參數的長度。concat函數連接self和s字符串。substr函數返回self參數從位置i到l的字串(從0 開始計算)。如果超界了,會產生一個運行時錯誤。
默認的初始化為""(不是void)。不能繼承或重定義String。
Bool
Bool類提供true和false。默認的初始化是false(不是void)。不能繼承或重定義Bool。
7,Main Class
每個程序必須有一個類Main,並且Main里必須有一個沒有參數的函數main。main函數必須在Main里定義(不能通過繼承其它類)。程序通過(new Main).main()來執行。
8,注釋 Comments
Cool里有兩種注釋。
一種是以"--"開頭,直到行尾都被注釋掉。
一種注釋以"(*"和"*)"包圍着。
—— Pinkman