#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);;
#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: 此表达式类型为 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
我们通过定义一个继承自点类的彩色点类来说明继承。这个类具有点类所有实例变量和所有方法,以及一个新的实例变量 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
允许多重继承。仅保留最后一个方法定义:子类中对父类可见方法的重新定义会覆盖父类中的定义。可以通过绑定相关的祖先来重用先前的方法定义。下面,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 ['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
以下展示了 circle 的另一种定义,它在类定义中使用了 constraint 子句。下面在 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
类 colored_circle 是类 circle 的一个专门版本,它要求中心的类型与 #colored_point 统一,并添加了一个名为 color 的方法。请注意,在专门化参数化类时,必须始终显式给出类型参数的实例。它再次写在 [ 和 ] 之间。
#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 的一个有效类型,并且它恰好与我们给出的多态类型不兼容(自动实例化仅适用于顶层类型变量,不适用于内部量词,在那里它变成了一个不可判定的问题)。因此,编译器无法在这两种类型之间进行选择,需要帮助。
#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];;
请注意对方法 times 使用 override。编写 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;;