3.16 バイナリメソッド

そのオブジェクト自体と同じ型の引数を取るメソッドのことをバイナリメソッドと言います。下の comparable クラスは型 'a -> bool なるバイナリメソッド leq を持つクラスのテンプレートです。ここで 'a はオブジェクト自体の型に束縛されています。したがって、 #comparable< leq : 'a -> bool; .. > as 'a の略記となります。ここで as が再帰的な型を表記するのに使えることが分ります。

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

さて、 comparable の子クラス money を定義しましょう。 money クラスは、単に浮動小数点数型を comparable オブジェクトになるようにしたものです。後で、もっと操作を加えることにします。 <= は Objective Caml では多相型を持つので、クラス引数 x には型制約が与えられています。 inherit 節によってこのクラスのオブジェクトが #comparable のインスタンスであることが保証されています。

#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
    

moneycomparable の部分型でないことに注意して下さい。というのは、自分自身の型が引数の位置に現れているからです。実際、クラス money のオブジェクト m のメソッド leq は引数の value メソッドを呼び出します。もし、m が型 comparable を持つとみなせたとすると、 value メソッドを持たないオブジェクトを引数として mleq メソッドを呼び出せることになり、エラーとなります。

同じように、次の型 money2money の部分型ではありません。

#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 のいずれの型を持つオブジェクトでも機能する関数を定義することができます。関数 min#comparable に単一化する型を持つ二つのオブジェクトのうち、最小のものを返します。 min の型は #comparable -> #comparable -> #comparable ではありません。というのは、#comparable は型変数 (.. の部分)を隠蔽するからです。これでは、それぞれの #comparable が新しい型変数を導入してしまいます。

#let min (x : #comparable) y =
   if x#leq y then x else y;;
val min : (#comparable as 'a) -> 'a -> 'a = <fun>
    

この関数は moneymoney2 のどちらの型をもつオブジェクトにも適用できます。

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

バイナリメソッドの他の例は 5.2.1 節「文字列 」 5.2.4 節「集合 」にあげられています。

times メソッドで {< ... >} 構文を用いていることに注意して下さい。 {< repr = k *. repr >} の代わりに new money2 (k *. repr) と書くと、継承をしたときにうまく動きません。すなわち、 money2 の子クラス money3times メソッドが期待した money3 クラスではなく money2 クラスのオブジェクトを返すようになってしまうのです。

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