nTest

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
OCaml的对象系统-1

OCaml的对象系统-1

1 简介

这篇文章是OCaml 4.00参考手册第3章中关于对象系统的学习笔记,不是完整 翻译。

注意OCaml中对象、类和类型的关系与其他主流的面向对象语言(Java, C++)非 常不同,因此不要假定同样的名词表示同样的意思。

2 类和对象

下面的类 point 定义了一个实例变量 x 和两个方法 get_xmove 。实例变量的初始值是0。变量 x 定义为可变,因此 move 方法可以修改 它的值。

class point =
object
  val mutable x = 0
  method get_x = x
  method move d = x <- x + d
end ;;
class point :
  object val mutable x : int method get_x : int method move : int -> unit end

创建一个 point 类的实例 p :

let p = new point;;
val p : point = <obj>
(* 注意p的类型是point. *)

(* 调用方法 *)
p#get_x;;  
p#move 3;;
p#get_x;;

只有在对象创建时才会对类进行求值:

let x0 = ref 0

class point =
object
  val mutable x = incr x0; !x0
  method get_x = x
  method move  d = x <- x + d
end;;
# new point#get_x;;
- : int = 1
# new point#get_x;;
- : int = 2

point 也可接受参数:

class point = fun x_init ->
object
  val mutable x = x_init
  method get_x = x
  method move d = x <- x + d
end;;

(* 和函数定义一样,可以简写为 *)
class point x_init =
object
  val mutable x = x_init
  method get_x = x
  method move d = x <- x + d
end;;

# new point;;
- : int -> point = <fun>
# let p = new point 7;;
val p : point = <obj>

参数 x_init 在整个类定义内都可见,包括方法。例如下面的 get_offset 方法返回当前坐标相对于初始位置的偏移:

class point x_init =
object
  val mutable x = x_init
  method get_x = x
  method get_offset = x - x_init
  method move d = x <- x + d
end;;

表达式可以在类的对象体之外求值和绑定。这常用来确定不变式。例如,坐标 可以自动调整为网格中最近的点:

class adjusted_point x_init =
  let origin = (x_init / 10) * 10 in
object
  val mutable x = origin
  method get_x = x
  method get_offset = x - origin
  method move d = x <- x + d
end;;

事实上,通过 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);;
val new_adjusted_point : int -> point = <fun>

不过,前面的形式更合适,因为调正代码是类定义的一部分,并且可以被继承。

这种功能像其它语言中类构造器。不同的构造器可以像这样定义不同的初始化 模式;另一种方式是使用初始化器。

3 直接对象

有一种更直接的创建对象的方法:不通过类直接创建它。

语法和类表达式很相似,但结果是一个单独对象而不是一个类。

let p =
object
  val mutable x = 0
  method get_x = x
  method move d = x <- x + d
end;;
val p : < get_x : int; move : int -> unit > = <obj>

# p#get_x;;
- : int = 0

# p#move 3;;
- : unit = ()

# p#get_x;;
- : int = 3

和类不能定义在表达式内不同,直接对象可以出现在任何地方,从他们的环境 中使用变量。

let minmax x y =
  if x < y then object method min = x method max = y end
  else object method min = y method max = x end
val minmax : 'a -> 'a -> < max : 'a; min : 'a > = <fun>

直接对象与类相比有两个弱点:他们的类型不是缩写的,并且你不能继承他们。 但是这两个弱点有时会变成优势。

4 引用自己

一个方法或初始化器可以给自己(也就是当前的对象)发送消息。为此,要显式 绑定self,这里使用变量 s ( s 可以是任意标识符,我们经常使用 self ):

class printable_point x_init =
object (s)
  val mutable x = x_init
  method get_x = x
  method print = print_int s#get_x
end;;

# let p = new printable_point 7;;
val p : printable_point = <obj>
# p#print;;
7- : unit = ()

动态地,变量 s 在调用一个方法时绑定。特别的,当继承 printable_point 类时,变量 s 将正确地绑定到子类的对象。

self的一个常见问题是,它的类型可能在子类中更改.如下示例:

let ints = ref []

class my_int =
object (self)
  method n = 1
  method register = ints := self :: !ints
end

Characters 72-76:
method register = ints := self :: !ints
                          ^^^^
Error: This expression has type < n : int; register : 'b; .. > as 'a
       but an expression was expected of type 'a
       This instance of < n : int; register : 'b; .. > is ambiguous:
       it would escape the scope of its equation

注意,直接对象不能扩展,所以不会出现这样的问题:

let my_int =
object (self)
  method n = 1
  method register = ints := self :: !ints
end;;
val my_int : < n : int; register : unit > = <obj>

5 初始化器

类定义中的let绑定在对象构造之前计算。也可能在对象构造之后立即对一个表达 式进行求值。 这些代码写在一个匿名的隐藏方法中,叫做初始化器。因此,它 可以访问self和实例变量。

class printable_point x_init =
  let origin = (x_init / 10) * 10 in
object (self)
  val mutable 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

# let p = new printable_point 17;;
new point at 10
val p : printable_point = <obj>

初始化器不能被覆盖。相反地,所有初始化器按顺序求值。初始化器常用来确 定不变式。

6 虚方法

可以使用关键字 virtual 声明一个方法但不定义它。这个方法稍后由子类提 供。 包含虚方法的类必须被标记为 virtual,并且不能初始化(也就是不能 创建这个类的对象)。 仍然需要定义类型缩写。

class virtual abstract_point x_init =
object (self)
  method virtual get_x : int
  method get_offset = self#get_x - x_init
  method virtual move : int -> unit
end

class point x_init =
object
  inherit abstract_point x_init
  val mutable x = x_init
  method get_x = x
  method move d = x <- x + d
end

(* 实例变量也可以被声明为virtual *)
class virtual abstract_point2 =
object
  val mutable virtual x : int
  method move d = x <- x + d
end
class point2 x_init =
object
  inherit abstract_point2
  val mutable x = x_init
  method get_offset = x - x_init
end

7 私有方法

私有方法在对象接口中不可见。他们只能被同一对象的其它方法调用。

class restricted_point x_init =
object (self)
  val mutable x = x_init
  method get_x = x
  method private move d = x <- x + d
  method bump = self#move 1
end

# p#move 10;;
Characters 0-1:
  p#move 10;;
  ^
Error: This expression has type restricted_point
       It has no method move
# p#bump;;
- : unit = ()

注意这同Java和C++的私有和保护方法不同,Java和C++中它们可以被同一个类 的其它对象调用。这是OCaml中类型和类之间不同的直接结果:两个不相关的类 可以产生同一类型的对象,并且从类型的级别上不能确定一个对象来自于哪个 特定的类。

私有方法是可以继承的,除非它们通过签名匹配隐藏, 也可以在子类中变成公共的:

class point_again x =
object (self)
  inherit restricted_point x
  method virtual move : _
end
class point_again :
  int ->
  object
    val mutable x : int
    method bump : unit
    method get_x : int
    method move : int -> unit
  end

# let p = new point_again 5;;
val p : point_again = <obj>
# p#move 3;;
- : unit = ()
# p#get_x;;
- : int = 8

这里的 virtual 标注只是用来说明一个方法没有提供它的定义。 没有加上 private 标注, 让 move 方法变成公共的,并保持原先的定义。

另一种定义方式:

class point_again x =
object ( self :<move:_; ..> )
  inherit restricted_point x
end
class point_again :
  int ->
  object
    val mutable x : int
    method bump : unit
    method get_x : int
    method move : int -> unit
  end

self的类型约束需要一个公共的 move 方法,这足够覆盖 private

可能有人认为一个私有方法在子类中必须还是私有的。然而,由于这个方法在 子类中可见,总是能使用它的代码定义一个同样的名字运行它,因此另一种解 决方案是:

class point_again x =
object
  inherit restricted_point x as super
  method move = super#move
end;;
class point_again :
  int ->
  object
    val mutable x : int
    method bump : unit
    method get_x : int
    method move : int -> unit
  end

当然,私有方法也可以是虚的。这时,关键字必须按这样的顺序出现: method private virtual

8 类接口

类接口从类定义中推导。也可以直接定义,用来限制一个类的类型。和类定义 一样,它们也定义一个新的类型缩写。

class type restricted_point_type =
object
  method get_x : int
  method bump : unit
end;;
class type restricted_point_type =
  object method bump : unit method get_x : int end

(* 新的类型缩写 *) 
fun (x:restricted_point_type) -> x;;    
- : restricted_point_type -> restricted_point_type = <fun>

具体的实例变量和具体的私有方法可以通过一个类的类型约束隐藏。公共方法 和虚方法不能。

class restricted_point' x = (restricted_point x : restricted_point_type);;
class restricted_point' : int -> restricted_point_type

(* 或者 *)
class restricted_point' = (restricted_point : int -> restricted_point_type);;
class restricted_point' : int -> restricted_point_type

一个类接口也可以在模块签名中指定,用来限制一个模块推导出的签名:

module type POINT = sig
  class restricted_point' : int ->
  object
    method get_x : int
    method bump : unit
  end
end;;
module type POINT =
  sig
    class restricted_point' :
      int -> object method bump : unit method get_x : int end
  end

module Point : POINT = struct
  class restricted_point' = restricted_point
end;;
module Point : POINT

9 继承

我们通过定义一个继承自point的类colored_point来演示继承。

class colored_point x (c : string) =
object
  inherit point x
  val c = c
  method color = c
end;;
class colored_point :
  int ->
  string ->
  object
    val c : string
    val mutable 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")

一个point和一个colored point拥有不兼容的类型,因为point没有 color 方法。下面的 get_x 是一个通用的函数,应用 get_x 方法到任意一个拥 有此方法的对象 p

let get_succ_x p = p#get_x + 1;;
val get_succ_x : < get_x : int; .. > -> int = <fun>

# p#get_x;;
- : int = 8
# p'#get_x;;
- : int = 5
# get_succ_x p + get_succ_x p';;
- : int = 15

方法并不需要事先声明,如下所示:

# 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>

10 多重继承

可以进行多重继承。只有最后一个方法的定义被保留:一个子类中重定义的方法在 父类中可见并覆盖父类的定义。

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 ->
  object
    val c : string
    val mutable 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>
# p'#print;;
(10, red)- : unit = ()

一个父类隐藏的私有方法不再可见,因此不能被覆盖。由于初始化器视为私有 方法,所有初始化器沿着类层次进行求值,按照它们引入的顺序。

11 参数化的类

引用单元可以使用对象实现。简单的定义会产生类型检查错误:

class ref 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 ref :
           '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 ref (x_init:int) =
object
  val mutable x = x_init
  method get = x
  method set y = x <- y
end;;

注意直接对象不定义一个class类型,它们没有这样的限制:

let new_ref x_init =
object
  val mutable x = x_init
  method get = x
  method set y = x <- y
end ;;
val new_ref : 'a -> < get : 'a; set : 'a -> unit > = <fun>

另一方面,一个类的多态引用必须在它的声明中显式列出类型参数。类型参数 在 [ ] 中列出。类型参数必须在类中的某个地方通过类型约束进行绑定:

class ['a] ref x_init =
object
  val mutable x = (x_init : 'a)
  method get = x
  method set y = x <- y
end;;
class ['a] ref :
  'a -> object val mutable x : 'a method get : 'a method set : 'a -> unit end

# let r = new ref 1 in r#set 2; r#get;;
- : int = 2

声明中的类型参数也可以被类定义进行约束。在这个class类型中,真实的类 型参数显示在 constraint 子句中:

class ['a] ref_succ (x_init : 'a) =
object
  val mutable x = x_init + 1
  method get = x
  method set y = x <- y
end;;
class ['a] ref_succ :
  'a ->
  object
    constraint 'a = int
    val mutable x : int
    method get : int
    method set : int -> unit
  end

考虑一个更复杂点的例子:定义一个圆,它的圆心可以是任何类型的point。我 们添加一个额外的类型约束 move 方法。

class ['a] circle (c : 'a) =
object
  val mutable center = c
  method center = center
  method set_center c = center <- c
  method move = (center#move : int -> unit)
end;;
class ['a] circle :
  'a ->
  object
    constraint 'a = < move : int -> unit; .. >
    val mutable 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; ..>

class ['a] circle (c : 'a) =
object
  constraint 'a = #point
  val mutable center = c
  method center = center
  method set_center c = center <- c
  method  move = center#move
end;;
class ['a] circle :
  'a ->
  object
    constraint 'a = #point
    val mutable center : 'a
    method center : 'a
    method move : int -> unit
    method set_center : 'a -> unit
  end

# let c = new circle (new colored_point 25 "red");;
val c : colored_point circle = <obj>
# c#center#color;;
- : string = "red"
# c#move 5;;
- : unit = ()
# c#center#get_x;;
- : int = 30

12 多态方法

尽管参数化的类可以在它们的上下文中多态,但它们不能完成多态方法。

典型的例子是定义一个迭代器:

List.fold_left;;

class ['a] intlist (l : int list) =
object
  method empty = (l = [])
  method fold f (accu : 'a) = List.fold_left f accu l
end;;
class ['a] intlist :
  int list ->
  object method empty : bool method fold : ('a -> int -> 'a) -> 'a -> 'a end

一眼看上去,我们有了一个多态的迭代器,但是这在实践中并不能工作。

let l = new intlist [1; 2; 3];;
val l : '_a intlist = <obj>

  l#fold (fun x y -> x + y) 0;;
- : int = 6
  l;;
- : int intlist = <obj>;;
  l#fold (fun s x -> s ^ string_of_int x ^ " ") "";;
Characters 19-20:
  l#fold (fun s x -> s ^ string_of_int x ^ " ") "";;
                     ^
Error: This expression has type int but an expression was expected of type
         string

  l#fold;;
- : (int -> int -> int) -> int -> int = <fun>


对象自己并不是多态的(只有构造器是),尝试当作字符串迭代器使用时会错误。

这里的问题是量化的位置错误:我们希望的不是类的多态,而是 fold 方法。 这可以通过在方法定义中给出显式多态类型来完成。

class intlist (l : int list) =
object
  method empty = (l = [])
  method fold : 'a. ('a -> int -> 'a) -> 'a -> 'a =
    fun f accu -> List.fold_left f accu l
end;;
class intlist :
  int list ->
  object method empty : bool method fold : ('a -> int -> 'a) -> 'a -> 'a end

let l = new intlist [1; 2; 3];;
l#fold (fun x y -> x + y) 0;;
l;;
l#fold (fun s x -> s ^ string_of_int x ^ " ") "";;

下面分离声明和实现:

class type ['a] iterator =
object method fold : ('b -> 'a -> 'b) -> 'b -> 'b end;;

class intlist l =
object (self : int #iterator)
  method empty = (l = [])
  method fold f accu = List.fold_left f accu l
end;;

注意这里的 (self : int #iterator) ,它保证了这个对象实现了接口 iterator

多态方法的调用和一般的方法一样,但是你需要知道一些类型推导的限制。也 就是说,一个多态方法只能在它的类型信息在调用的地方已知的情况下被调用。否则, 这个方法将假定为单态的,并给出一个不兼容的类型。

let sum lst = lst#fold (fun x y -> x + y) 0;;
val sum : < fold : (int -> int -> int) -> int -> 'a; .. > -> 'a = <fun>
;;
# l#fold;;
- : ('_a -> int -> '_a) -> '_a -> '_a = <fun>
# sum l;;
Characters 4-5:
  sum l;;
      ^
Error: This expression has type intlist
       but an expression was expected of type
         < fold : (int -> int -> int) -> int -> 'a; .. >
       Types for method fold are incompatible

变通方案很简单:在参数中给出类型约束:

let sum (lst : _ # iterator) = lst#fold (fun x y -> x + y) 0;;
val sum : int #iterator -> int = <fun>

(* 或者 *)  
let sum lst =
  (lst : < fold : 'a. ('a -> _ -> 'a) -> 'a -> 'a; ..>)#fold (+) 0;;
val sum : < fold : 'a. ('a -> int -> 'a) -> 'a -> 'a; .. > -> int = <fun>

另一种多态方法的用途是允许在参数中使用一些隐式子类型:

class type point0 = object method get_x : int end;;

class distance_point x =
object
  inherit point x
  method distance : 'a. (#point0 as 'a) -> int =
    fun other -> abs (other#get_x - x)
end;;
class distance_point :
  int ->
  object
    val mutable 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)

class multi_poly =
object
  method 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;;
class multi_poly :
  object
    method m1 : < n1 : 'b. 'b -> 'b; .. > -> bool * string
    method m2 : < n2 : 'b -> bool; .. > -> 'b -> bool
  end

13 使用强制转换

子类化从不是隐式的.有两种方式完成子类化,最常用的构造是完全显式:类型强制 转换的域(domain)和上域(codomain)的都必须给出.

# let  colored_point_to_point cp = (cp : colored_point :> point);;
val colored_point_to_point : colored_point -> point = <fun>

# let p = new point 3;;
and q = new colored_point 4 "blud";;
val p : point = <obj>
val q : colored_point = <obj>
# let l = [p; (colored_point_to_point q)];;
val l : point list = [<obj>; <obj>]
;;
(p : point :> colored_point);;
Characters 0-28:
  (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 > 

一个对象类型 t 是t'的子类型时,才可以当作一个对象类型 t';例如,一个 point不能当作一个colored point.

实际上,没有运行时检查的向下(narrowing)强制类型转换是不安全的.运行时类型检查可能 引发异常,并且需要运行时的类型信息表示,这与OCaml的系统不符.因此,不能 进行这些操作.

注意子类型(subtyping)和继承(inheritance)是不相关的.继承是类(classes) 之间的一个语法关系,子类型是类型(types)之间的一个语义关系.例如,colored point可以直接定义,不需要从point类继承;colored point的类型仍然不变,因 此它还是point的一个子类型.

class就是一个模板,创造出的对象(object)的类型(type)就是这个类的类型. 但跟这个类没关系,跟类的类型有关系,两个类可以有同样的类型,那么这两个 类创建的对象就是类型相同的. subtyping是针对对象的类型的, inheritance 是针对类的(方便写模板而已,所以是语法上的)).

强制转换的域常常可以忽略.

let to_point cp = (cp :> point);;
val to_point : #point -> point = <fun>

函数 colored_point_to_pointto_point 的一个实例.然而,并不 总是这样.完全显式强制转换更严格,并且有时不可避免.例如:

class c0 = object method m = {<>} method n = 0 end;;
class c0 : object ('a) method m : 'a method n : int end

class type c1 = object method m : c1 end;;

# fun (x) -> (x :> c1);;
- : < m : #c1; .. > -> c1 = <fun>
# fun (x:c0) -> (x : c0 :> c1);;
- : c0 -> c1 = <fun>
;;
fun (x:c0) -> (x :> c1);;
- : c0 -> c1 = <fun>
;;
class type c2 = object ('a) method m : 'a end;;

fun (x:c0) -> (x :> c2);;
- : c0 -> c2 = <fun>
;;
let to_c1 x = (x :> c1);;
val to_c1 : < m : #c1; .. > -> c1 = <fun>
# let to_c2 x = (x :> c2);;
val to_c2 : #c2 -> c2 = <fun>

一个常见的问题发生在试图在定义类c的时候定义一个强制转换到类c。这是因 为类型缩写还没有完全定义,因此,它的子类型不是全部已知的。因此,一个 约束 (_ :> c)(_ : #c :> c) 等同于以下恒等式:

function x -> (x :> 'a);;
- : 'a -> 'a = <fun>

因此,如果强制转换应用到 self ,如下示例, self 与 封闭类型 c 一 致(一个封闭的对象类型是一个没有省略号的对象类型)。实际上, self 的 类型不能被封闭:这将导致类不能被扩展。因此,产生类型错误:

class c = object method m = 1 end
and d = object (self)
  inherit c
  method n = 2
  method as_c = (self :> c)
end;;
Characters 100-104:
    method as_c = (self :> c)
                   ^^^^
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 be unified with a closed object type

常用的解决方法,强制转换self到它的当前类,将作为类型检查的一个特例,并给予正 确的类型:

class c = object (self) method m = (self :> c) end;;
class c : object method m : c end

这就允许下面的用法,用一个列表保存一个类或它的子类的所有对象:

let all_c = ref [];;
val all_c : '_a list ref = {contents = []}

;;
class c (m : int) =
object (self)
  method m = m
  initializer all_c := (self :> c) :: !all_c
end
class c : int -> object method m : int end
;;
let rec lookup_obj obj = function
  | [] -> raise Not_found
  | obj' :: l ->
    if (obj :> < >) = (obj' :> < >) then obj'
    else lookup_obj obj l
;;
val lookup_obj : < .. > -> (< .. > as 'a) list -> 'a = <fun>
;;
let lookup_c obj = lookup_obj obj !all_c;;
val lookup_c : < .. > -> < m : int > = <fun>
;;
# let c1 = new c 5;;
val c1 : < m : int > = <obj>
# let c2 = new c 6;;
val c2 : < m : int > = <obj>
# let p1 = new point 7;;
val p1 : point = <obj>
# lookup_c p1;;
Exception: Not_found.
# lookup_c c1;;
- : < m : int > = <obj>

类型 < m : int> 只是 c 的展开形式,通过使用一个引用,我们成功找 回一个类型为 c 的对象。

强制转换问题的另一种解决方法是先使用 class type 定义缩写:

class type c' = object method m : int end;;

class c : c' = object method m = 1 end
and d = object (self)
  inherit c
  method n = 2
  method as_c = (self :> c')
end;;
class c : c'
and d : object method as_c : c' method m : int method n : int end
;;
# let d1 = new d;;
val d1 : d = <obj>
# d1#as_c;;
- : c' = <obj>

也可以通过虚类完成。从此类继承同时强制所有 c 的方法与 c' 的方法 类型相同。

class virtual c' = object method virtual m : int end;;

class c = object (self) inherit c' method m = 1 end;;
class c : object method m : int end

有人可能会想直接定义类型缩写:

type c' = <m : int>;;

但是,缩写 #c' 不能直接像这样定义。它只能由一个类(class)或类类型 (class-type)定义。这是因为 # 缩写传递一个隐式变量 .. 不能被直接命名。 下面的更接近:

type 'a c'_class = 'a constraint 'a = < m : int; ..>;;

14 函数式对象

可以写一个不用对实例变量赋值的另一个版本的 point 类。覆盖构造器 {< … >} 返回一个 "self" 的拷贝,并可以修改一些实例变量的值。

class functional_point y =
object
  val x = y
  method get_x = x
  method move d = {< x = x + d >}
end;;
class functional_point :
  int ->
  object ('a) val x : int method get_x : int method move : 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#get_x;;
- : int = 7

注意 functional_point 的类型缩写是递归的, self的类型是'a,并且'a 出现在 move 方法的类型中。

上面的 functional_point 定义与下面的并不相等:

class bad_functional_point y =
object
  val x = y
  method get_x = x
  method move d = new bad_functional_point (x + d)
end;;
class bad_functional_point :
  int ->
  object
    val x : int
    method get_x : int
    method move : int -> bad_functional_point
  end

尽管两个类的对象有相同的行为,但它们子类的对象将不同。在 bad_functional_point 的子类中, move 方法仍然返回一个父类的对象。 与此相反, functional_point 的子类中, move 方法将返回一个子类的 对象。

函数式更新经常和二元方法结合使用。

15 克隆对象

对象也可以克隆,不管它们是函数式的或指令式的。库函数 Oo.copy 返回 一个对象的浅拷贝。 也就是说,它返回一个和它的参数具有相同的方法和实 例变量的新对象。虽然实例变量复制了,但它们的内容是共享的。对拷贝的实 例变量进行赋值并不会影响原来的实例变量,反之也一样。 一个深层赋值(例 如实例变量是一个引用)将同时影响原来的和拷贝的。

Oo.copy;;
- : (< .. > as 'a) -> 'a = <fun>

类型中的 as 关键字绑定类型变量 'a 到对象类型 < .. > .因此, Oo.copy 接受一个含有任意方法的对象(表示为省略号),并返回一个同样类 型的对象。

# let p = new point 5;;
val p : point = <obj>
# let q = Oo.copy p;;
val q : point = <obj>
# q#move 7; (p#get_x, q#get_x);;
- : int * int = (5, 12)

事实上,假定类 p 中定义一个内容为 {< >} 的公共 copy 方法; Oo.copy pp#copy 的行为相同。

对象可以通过通用比较函数 =<> 进行比较。两个对象如果是物理相 等的就相等。 实际上,一个对象和它的拷贝并不相等:

# p = q, p = p;;
- : bool * bool = (false, true)

另一些通用比较( <, <=, …)也可以用于对象。关系 < 在对象上定义 了一个未指定但严格的顺序。两个对象一旦被创建,顺序关系就固定了,并且 不会被可变字段影响。

克隆和覆盖有一个非空交集。当不覆盖任何字段的时候,它们可互换:

class copy =
object
  method copy = {< >}
end;;
class copy : object ('a) method copy : 'a end

class copy =
object (self)
  method copy = Oo.copy self
end;;
class copy : object ('a) method copy : 'a end

只有覆盖可以覆盖字段,只有 Oo.copy 可以在外部使用。

克隆也可用来提供对象状态的保存或恢复。

class backup =
object (self : 'mytype)
  val mutable copy = None
  method save = copy <- Some {< copy = None >}
  method restore = match copy with
  | Some x -> x
  | None -> self
end;;
class backup :
  object ('a)
    val mutable copy : 'a option
    method restore : 'a
    method save : unit
  end

上面的定义只能备份一级。备份工具可以通过多重继承添加到任何类。

class ['a] backup_ref x = object inherit ['a] ref x inherit backup end;;
class ['a] backup_ref :
  'a ->
  object ('b)
    val mutable copy : 'b option
    val mutable x : 'a
    method get : 'a
    method restore : 'b
    method save : unit
    method set : 'a -> unit
  end
;;
let rec 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]

我们可以定义一个backup的变种来获得所有拷贝。(也需要添加一个 clear 方法用来手动清除所有拷贝)。

class backup =
object (self : 'mytype)
  val mutable 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)
    val mutable copy : 'a option
    method clear : unit
    method restore : 'a
    method save : unit
  end
;;
class ['a] backup_ref x = object inherit ['a] ref x inherit backup end;;
class ['a] backup_ref :
  'a ->
  object ('b)
    val mutable copy : 'b option
    val mutable 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];;
 - : int list = [2; 1; 0; 0; 0]

16 递归的类

class window =
object
  val mutable top_widget = (None : widget option)
  method top_widget = top_widget
end
and widget (w : window) =
object
  val window = w
  method window = window
end;;
class window :
  object
    val mutable top_widget : widget option
    method top_widget : widget option
  end
and widget : window -> object val window : window method window : window end

尽管它们的类型相互递归,但是类 widgetwindow 本身是互相独立的。

17 二元方法

二元方法是一个接受一个与自己类型相同的参数的方法。

class virtual comparable =
object (_ : 'a)
  method virtual leq : 'a -> bool
end;;

class money (x : float) =
object
  inherit 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 类型的子类型。一个 money 类的 对象 m 拥有一个接受一个 money 类型参数的 leq 方法,因为它要访问它 的 value 方法。

同样地,下面的 money2 类型不是 money 类型的子类型:

class money2 x =
object
  inherit 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

可以定义一个函数操作 moneymoney2 类型的对象:

let min (x : #comparable) y =
  if x#leq y then x else y;;
val min : (#comparable as 'a) -> 'a -> 'a = <fun>
;;
# (min (new money 1.3) (new money 3.1))#value;;
- : float = 1.3
# (min (new money2 5.0) (new money2 3.14))#value;;
- : float = 3.14

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

18 友元

上面的 money 类暴露了一个二元方法常见的问题。为了与同一个类的其它 对象交互, money 对象的表示必须暴露出来,使用一个类似于 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

我们可以使用模块系统来限制这些表示的可见性:

module type MONEY =
  sig
    type 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
    end
  end
;;

module Euro : MONEY =
  struct
    type 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 >}
    end
  end;;
# let m1 = new Euro.c 5.;;
val m1 : Euro.c = <obj>
# m1#value;;
- : Euro.t = <abstr>

这些示例发生在一组对象和函数需要相互看到其内部表示,而这些内部表示需 要对外部隐藏的时候。解决方法就是把所有友元都定义到一个模块中,提供表 示并使用一个签名约束让这些表示在模块外部变成抽象的。

Date: 2013-02-13 星期三

Author: nTest

Org version 7.8.11 with Emacs version 24

Validate XHTML 1.0
posted on 2013-02-28 14:56  nTest  阅读(845)  评论(0)    收藏  举报