#class adjusted_point x_init = let origin = (x_init / 10) * 10 inobjectvalmutable x = origin method get_x = x method get_offset = x - origin method move d = x <- x + d end;;
class adjusted_point : int -> objectvalmutable x : int method get_offset : int method get_x : int method move : int -> unit end
(如果 x_init 座標不在網格上,也可以引發例外狀況。)事實上,這裡可以藉由使用 origin 的值呼叫類別 point 的定義來獲得相同的效果。
#class adjusted_point x_init = point ((x_init / 10) * 10);;
class adjusted_point : int -> point
另一種替代方案是在特殊的配置函式中定義調整
#let new_adjusted_point x_init = new point ((x_init / 10) * 10);;
類別定義中的 let 綁定會在物件建構之前進行評估。也可以在物件建構完成後立即評估表達式。此類程式碼會被寫成稱為初始化器的匿名隱藏方法。因此,它可以存取 self 和實例變數。
#class printable_point x_init = let origin = (x_init / 10) * 10 inobject (self) valmutable x = origin method get_x = x method move d = x <- x + d method print = print_int self#get_x initializer print_string "new point at "; self#print; print_newline () end;;
class printable_point : int -> objectvalmutable x : int method get_x : int method move : int -> unit method print : unit end
#class restricted_point x_init = object (self) valmutable x = x_init method get_x = x methodprivate move d = x <- x + d method bump = self#move 1 end;;
class restricted_point : int -> objectvalmutable x : int method bump : unit method get_x : int methodprivate move : int -> unit end
#let p = new restricted_point 0;;
val p : restricted_point = <obj>
#p#move 10 ;;
Error: This expression has type restricted_point 它沒有方法 move
# p#bump;;
- : unit = ()
請注意,這與 Java 或 C++ 中的私有和受保護方法不同,後者可以從同類別的其他物件中調用。這是 OCaml 中類型和類別之間獨立性的直接結果:兩個不相關的類別可能會產生相同類型的物件,並且在類型層級上無法確保物件來自特定的類別。然而,3.17 節中給出了友元方法的可能編碼。
私有方法會被繼承(它們預設在子類別中可見),除非它們被簽章匹配隱藏,如下所述。
私有方法可以在子類別中設為公開。
#class point_again x = object (self) inherit restricted_point x methodvirtual move : _ end;;
class point_again : int -> objectvalmutable x : int method bump : unit method get_x : int method move : int -> unit end
我們透過定義一個繼承自點類別的彩色點類別來說明繼承。此類別具有類別 point 的所有實例變數和所有方法,以及一個新的實例變數 c 和一個新的方法 color。
#class colored_point x (c : string) = objectinherit point x val c = c method color = c end;;
class colored_point : int -> string -> objectval c : string valmutable x : int method color : string method get_offset : int method get_x : int method move : int -> unit end
#let p' = new colored_point 5 "red";;
val p' : colored_point = <obj>
# p'#get_x, p'#color;;
- : int * string = (5, "red")
點和彩色點具有不相容的類型,因為點沒有 color 方法。但是,下面的 get_x 函數是一個通用函數,它將 get_x 方法應用於任何具有此方法(以及可能其他一些方法,這些方法在類型中以省略號表示)的物件 p。因此,它適用於點和彩色點。
#let get_succ_x p = p#get_x + 1;;
val get_succ_x : < get_x : int; .. > -> int = <fun>
# get_succ_x p + get_succ_x p';;
- : int = 8
方法不需要事先宣告,如以下範例所示
#let set_x p = p#set_x;;
val set_x : < set_x : 'a; .. > -> 'a = <fun>
#let incr p = set_x p (get_succ_x p);;
val incr : < get_x : int; set_x : int -> 'a; .. > -> 'a = <fun>
允許使用多重繼承。只保留方法的最後一個定義:在子類別中重新定義父類別中可見的方法會覆寫父類別中的定義。先前的方法定義可以透過繫結相關的祖先來重複使用。在下面,super 被繫結到祖先 printable_point。名稱 super 是一個偽值識別符號,只能用於呼叫超類別方法,如 super#print。
#class printable_colored_point y c = object (self) val c = c method color = c inherit printable_point y as super method! print = print_string "("; super#print; print_string ", "; print_string (self#color); print_string ")"end;;
class printable_colored_point : int -> string -> objectval c : string valmutable x : int method color : string method get_x : int method move : int -> unit method print : unit end
#let p' = new printable_colored_point 17 "red";;
new point at (10, red) val p' : printable_colored_point = <obj>
#class another_printable_colored_point y c c' = object (self) inherit printable_point y inherit! printable_colored_point y c val! c = c' end;;
class another_printable_colored_point : int -> string -> string -> objectval c : string valmutable x : int method color : string method get_x : int method move : int -> unit method print : unit end
#class oref x_init = object val mutable x = x_init method get = x method set y = x <- y end;;
Error: Some type variables are unbound in this type: class oref : 'a -> object val mutable x : 'a method get : 'a method set : 'a -> unit end The method get has type 'a where 'a is unbound
#class ['a] circle (c : 'a) = objectvalmutable center = c method center = center method set_center c = center <- c method move = (center#move : int -> unit) end;;
class ['a] circle : 'a -> objectconstraint 'a = < move : int -> unit; .. > valmutable center : 'a method center : 'a method move : int -> unit method set_center : 'a -> unit end
下面顯示了使用類別定義中的 constraint 子句的 circle 的替代定義。下面在 constraint 子句中使用的型別 #point 是由類別 point 的定義產生的縮寫。此縮寫會與屬於類別 point 子類別的任何物件的型別統一。它實際上會展開為 < get_x : int; move : int -> unit; .. >。這導致了 circle 的以下替代定義,它對其引數具有稍微更強的約束,因為我們現在期望 center 具有方法 get_x。
#class ['a] circle (c : 'a) = objectconstraint 'a = #point valmutable center = c method center = center method set_center c = center <- c method move = center#move end;;
class ['a] circle : 'a -> objectconstraint 'a = #point valmutable center : 'a method center : 'a method move : int -> unit method set_center : 'a -> unit end
#class ['a] colored_circle c = objectconstraint 'a = #colored_point inherit ['a] circle c method color = center#color end;;
class ['a] colored_circle : 'a -> objectconstraint 'a = #colored_point valmutable center : 'a method center : 'a method color : string method move : int -> unit method set_center : 'a -> unit end
#class intlist (l : int list) = objectmethod empty = (l = []) method fold : 'a. ('a -> int -> 'a) -> 'a -> 'a = fun f accu -> List.fold_left f accu l end;;
class intlist : int list -> objectmethod empty : bool method fold : ('a -> int -> 'a) -> 'a -> 'a end
#let l = new intlist [1; 2; 3];;
val l : intlist = <obj>
# l#fold (fun x y -> x+y) 0;;
- : int = 6
# l#fold (fun s x -> s ^ Int.to_string x ^ " ") "";;
- : string = "1 2 3 "
正如您在編譯器顯示的類別型別中所看到的,雖然多型方法型別在類別定義中必須完全明確(出現在方法名稱之後),但量化的型別變數可以在類別描述中保持隱含。為什麼要求型別明確?問題在於 (int -> int -> int) -> int -> int 也會是 fold 的有效型別,並且它恰好與我們給定的多型型別不相容(自動實例化僅適用於頂層型別變數,而不適用於內部量詞,在內部量詞中,它會變成一個無法判定的問題。)因此,編譯器無法在兩個型別之間選擇,必須有人協助。
val sum : < fold : (int -> int -> int) -> int -> 'a; .. > -> 'a = <fun>
# sum l ;;
Error: This expression has type intlist but an expression was expected of type < fold : (int -> int -> int) -> int -> 'b; .. > The method fold has type 'a. ('a -> int -> 'a) -> 'a -> 'a, but the expected method type was (int -> int -> int) -> int -> 'b
解決方法很簡單:您應該在參數上加上類型約束。
#let sum (lst : _ #iterator) = lst#fold (fun x y -> x+y) 0;;
#classtype point0 = objectmethod get_x : int end;;
classtype point0 = objectmethod get_x : int end
#class distance_point x = objectinherit point x method distance : 'a. (#point0 as 'a) -> int = fun other -> abs (other#get_x - x) end;;
class distance_point : int -> objectvalmutable x : int method distance : #point0 -> int method get_offset : int method get_x : int method move : int -> unit end
#let p = new distance_point 3 in (p#distance (new point 8), p#distance (new colored_point 1 "blue"));;
- : int * int = (5, 2)
請注意此處我們必須使用的特殊語法 (#point0 as 'a),以量化 #point0 的可擴展部分。至於變數綁定器,它可以在類別規格中省略。如果要在物件欄位內使用多型,則必須獨立量化。
#class multi_poly = objectmethod m1 : 'a. (< n1 : 'b. 'b -> 'b; .. > as 'a) -> _ = fun o -> o#n1 true, o#n1 "hello"method m2 : 'a 'b. (< n2 : 'b -> bool; .. > as 'a) -> 'b -> _ = fun o x -> o#n2 x end;;
val colored_point_to_point : colored_point -> point = <fun>
#let p = new point 3 and q = new colored_point 4 "blue";;
val p : point = <obj> val q : colored_point = <obj>
#let l = [p; (colored_point_to_point q)];;
val l : point list = [<obj>; <obj>]
只有當 t 是 t' 的子類型時,才能將類型為 t 的物件視為類型為 t' 的物件。例如,不能將點視為彩色點。
#(p : point :> colored_point);;
Error: Type point = < get_offset : int; get_x : int; move : int -> unit > is not a subtype of colored_point = < color : string; get_offset : int; get_x : int; move : int -> unit > The first object type has no method color
請注意這兩個強制轉換之間的差異:在 to_c2 的情況下,類型 #c2 = < m : 'a; .. > as 'a 是多型遞迴的(根據 c2 的類別類型中的明確遞迴);因此,將此強制轉換應用於類別 c0 的物件是成功的。另一方面,在第一種情況下,c1 僅展開和展開兩次以獲得 < m : < m : c1; .. >; .. > (請記住 #c1 = < m : c1; .. >),而沒有引入遞迴。您也可以注意到,to_c2 的類型是 #c2 -> c2,而 to_c1 的類型比 #c1 -> c1 更通用。這並非總是如此,因為有些類別類型的 #c 的某些實例不是 c 的子類型,如第3.16節中所述。然而,對於無參數類別,強制轉換 (_ :> c) 始終比 (_ : #c :> c) 更通用。
當人們嘗試定義對類別 c 的強制轉換,同時定義類別 c 時,可能會發生常見的問題。問題的原因在於類型縮寫尚未完全定義,因此其子類型不明確。然後,強制轉換 (_ :> c) 或 (_ : #c :> c) 被視為恆等函式,如下所示:
#fun x -> (x :> 'a);;
- : 'a -> 'a = <fun>
因此,如果將強制轉換應用於 self,如下例所示,則 self 的類型會與封閉類型 c 統一(封閉物件類型是不帶省略符號的物件類型)。這會約束 self 的類型為封閉類型,因此被拒絕。實際上,self 的類型不能是封閉的:這會阻止該類別的任何進一步擴展。因此,當此類型與另一類型的統一導致封閉物件類型時,會產生類型錯誤。
#class c = objectmethod m = 1 endand d = object (self) inherit c method n = 2 method as_c = (self :> c) end;;
Error: This expression cannot be coerced to type c = < m : int >; it has type < as_c : c; m : int; n : int; .. > but is here used with type c Self type cannot escape its class
可以編寫一個不對實例變數進行賦值的類別 point 版本。覆蓋建構 {< ... >} 會傳回「self」(也就是目前的物件)的副本,可能會更改某些實例變數的值。
#class functional_point y = objectval x = y method get_x = x method move d = {< x = x + d >} method move_to x = {< x >} end;;
class functional_point : int -> object ('a) val x : int method get_x : int method move : int -> 'a method move_to : int -> 'a end
#let p = new functional_point 7;;
val p : functional_point = <obj>
# p#get_x;;
- : int = 7
# (p#move 3)#get_x;;
- : int = 10
# (p#move_to 15)#get_x;;
- : int = 15
# p#get_x;;
- : int = 7
如同記錄一樣,{< x >} 的形式是 {< x = x >} 的省略版本,避免重複使用實例變數名稱。請注意,類型縮寫 functional_point 是遞迴的,這可以在 functional_point 的類別類型中看到:self 的類型是 'a,而 'a 出現在方法 move 的類型中。
上面 functional_point 的定義與以下定義不等價
#class bad_functional_point y = objectval x = y method get_x = x method move d = new bad_functional_point (x+d) method move_to x = new bad_functional_point x end;;
class bad_functional_point : int -> objectval x : int method get_x : int method move : int -> bad_functional_point method move_to : int -> bad_functional_point end
#class backup = object (self : 'mytype) valmutable copy = None method save = copy <- Some {< copy = None >} method restore = match copy with Some x -> x | None -> self end;;
class backup : object ('a) valmutable copy : 'a option method restore : 'a method save : unit end
上述定義只會備份一層。可以使用多重繼承將備份功能新增到任何類別。
#class ['a] backup_ref x = objectinherit ['a] oref x inherit backup end;;
class ['a] backup_ref : 'a -> object ('b) valmutable copy : 'b option valmutable x : 'a method get : 'a method restore : 'b method save : unit method set : 'a -> unit end
#letrec get p n = if n = 0 then p # get else get (p # restore) (n-1);;
val get : (< get : 'b; restore : 'a; .. > as 'a) -> int -> 'b = <fun>
#let p = new backup_ref 0 in p # save; p # set 1; p # save; p # set 2; [get p 0; get p 1; get p 2; get p 3; get p 4];;
- : int list = [2; 1; 1; 1; 1]
我們可以定義一個保留所有副本的備份變體。(我們還新增了一個方法 clear 來手動清除所有副本。)
#class backup = object (self : 'mytype) valmutable copy = None method save = copy <- Some {< >} method restore = match copy with Some x -> x | None -> self method clear = copy <- None end;;
class backup : object ('a) valmutable copy : 'a option method clear : unit method restore : 'a method save : unit end
#class ['a] backup_ref x = objectinherit ['a] oref x inherit backup end;;
class ['a] backup_ref : 'a -> object ('b) valmutable copy : 'b option valmutable x : 'a method clear : unit method get : 'a method restore : 'b method save : unit method set : 'a -> unit end
#let p = new backup_ref 0 in p # save; p # set 1; p # save; p # set 2; [get p 0; get p 1; get p 2; get p 3; get p 4];;
二元方法是一種將自身類型作為參數的方法。下面的 comparable 類別是一個範本,用於具有二元方法 leq 的類別,其類型為 'a -> bool,其中類型變數 'a 綁定到自身的類型。因此,#comparable 會展開為 < leq : 'a -> bool; .. > as 'a。我們在這裡看到綁定符 as 也允許寫入遞迴類型。
#class money (x : float) = objectinherit comparable val repr = x method value = repr method leq p = repr <= p#value end;;
class money : float -> object ('a) val repr : float method leq : 'a -> bool method value : float end
請注意,類型 money 不是類型 comparable 的子類型,因為自身類型以逆變的位置出現在方法 leq 的類型中。實際上,類別 money 的物件 m 具有一個方法 leq,該方法期望類型為 money 的參數,因為它會存取其 value 方法。將 m 視為類型 comparable 會允許使用不具有方法 value 的參數來呼叫 m 上的方法 leq,這將會是一個錯誤。
同樣地,下面的類型 money2 也不是類型 money 的子類型。
#class money2 x = objectinherit money x method times k = {< repr = k *. repr >} end;;
class money2 : float -> object ('a) val repr : float method leq : 'a -> bool method times : float -> 'a method value : float end
請注意方法 times 中使用的覆寫。寫入 new money2 (k *. repr) 而不是 {< repr = k *. repr >} 在繼承方面會表現不好:在 money2 的子類別 money3 中,times 方法將會回傳類別 money2 的物件,而不是預期的類別 money3 的物件。
money 類別自然可以攜帶另一個二元方法。這是一個直接的定義
#class money x = object (self : 'a) val repr = x method value = repr method print = print_float repr method times k = {< repr = k *. x >} method leq (p : 'a) = repr <= p#value method plus (p : 'a) = {< repr = x +. p#value >} end;;
class money : float -> object ('a) val repr : float method leq : 'a -> bool method plus : 'a -> 'a method print : unit method times : float -> 'a method value : float end
上面的類別 money 揭示了二元方法中經常出現的問題。為了與相同類別的其他物件互動,必須使用類似於 value 的方法來揭示 money 物件的表示形式。如果我們移除所有二元方法(此處為 plus 和 leq),則可以透過也移除 value 方法,輕鬆地將表示形式隱藏在物件內部。但是,只要某些二元方法需要存取相同類別(而非自身)的物件表示形式,就不可能這樣做。
#class safe_money x = object (self : 'a) val repr = x method print = print_float repr method times k = {< repr = k *. x >} end;;
class safe_money : float -> object ('a) val repr : float method print : unit method times : float -> 'a end
#moduletype MONEY = sigtype t class c : float -> object ('a) val repr : t method value : t method print : unit method times : float -> 'a method leq : 'a -> bool method plus : 'a -> 'a endend;;
#module Euro : MONEY = structtype t = float class c x = object (self : 'a) val repr = x method value = repr method print = print_float repr method times k = {< repr = k *. x >} method leq (p : 'a) = repr <= p#value method plus (p : 'a) = {< repr = x +. p#value >} endend;;