3.14 オブジェクトの複製

関数型のオブジェクトも命令型のオブジェクトも、どちらも複製することができます。ライブラリ関数の Oo.copy はオブジェクトの「浅い」コピーを作ります。つまり、もとのオブジェクトと同じ内容のオブジェクトを返します。インスタンス変数はコピーされますが、その内容は共有されます。複製されたオブジェクトのインスタンス変数を(メソッド呼び出しを介して)変更しても、もとのオブジェクトのインスタンス変数は変化しません。逆の場合同じです。もちろん、より深いレベルでの代入(例えばインスタンス変数が参照セルであった場合)は、もとのオブジェクトと複製の両方に影響を与えます。

Oo.copy の型は次のようになります。

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

型に現れる as というキーワードは型変数 'a をオブジェクトの型 < .. > に束縛します。したがって、 Oo.copy は任意のメソッド(.. で表されています)を持つオブジェクトを引数に取り、同じ型のオブジェクトを返すことになります。 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 と同じように振舞います。

オブジェクトは汎用比較関数 =<> を使って比較することができます。ふたつのオブジェクトが等しいのは、オブジェクト同士が物理的に等しい場合です。特に、オブジェクトとその複製は等しくありません。

#let q = Oo.copy p;;
val q : point = <obj>

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

<<= といった他の汎用の比較関数もオブジェクトに対して用いることができます。 < の意味は規定されていませんが、全順序を与えることが保証されています。オブジェクト間の順序は、オブジェクトの生成時に決定され、インスタンス変数が変化しても順序が変化することはありません。

Oo.copy{< >} は似た所があります。どちらも、オブジェクト内で、インスタンス変数を変えない自分自身のコピーを生成するのに使えます。

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

すべてのコピーを保持するようなバックアップ機能を考えることもできます(すべてのバックアップを除去するメソッドを付け加えました)。

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