3.10 パラメータ化されたクラス

参照セルはオブジェクトとして実装できます。しかし、素朴な定義は型チェックを通過できません。

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

直接生成されたオブジェクトはクラス型を定義しないので、この制限はありません。

#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
    

宣言中の型パラメータが実際にクラス定義の本体で制約を受けるかもしれません。クラス型中では、型パラメータの実際の値は 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
    

もっと複雑な例を考えましょう。「点」オブジェクトと、その任意のサブオブジェクトを中心とする「円」を定義することを考えましょう。 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
    

他にも、以下に示すように constraint 節を用いて circle クラスを定義することもできます。 constraint 節で用いられている #point という型は、 point クラスを定義する時に自動的に定義されます。この型は point クラスの任意の子クラスに属するオブジェクトと単一化します。 #point は実際には < get_x : int; move : int -> unit; .. > という型の略記です。この表記を用いると、次のような circle クラスの定義を得ることができます。このクラス定義は、引数に対して若干以前よりも強い制約を科します。というのは centerget_x メソッドを持つことを求めているからです。

#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
    

colored_circle クラスは circle クラスの特殊化したもので、「中心」の型が #colored_point に単一化されることを求めます。また、 color メソッドを持っています。パラメータ付きクラスを継承する場合、型変数を再び明示する必要があります。型変数は、以前と同様に [] の間に書きます。

#class ['a] colored_circle c =
   object
     constraint 'a =  colored_point
     inherit ['a] circle c
     method color = center color
   end;;
class ['a] colored_circle :
  'a ->
  object
    constraint 'a = #colored_point
    val mutable center : 'a
    method center : 'a
    method color : string
    method move : int -> unit
    method set_center : 'a -> unit
  end