7

OCAML オブジェクトのこの動作の理由を理解したいと思います。A別のクラスのオブジェクトのメソッドを呼び出すクラスがあるとしますB。概略的には、A#f は B#g と B#h を呼び出します。OOP では通常、B を固定具象クラスとして使用することは避け、代わりに B のインターフェイスのみを宣言します。OCAML でこれを行う最善の方法は何ですか? いくつかのオプションを試しましたが、機能するものと機能しないものがある理由がよくわかりません。コードサンプルを次に示します。

バージョン 1:

 # class classA = object
    method f b = b#g + b#h 
   end ;;
 Error: Some type variables are unbound in this type:
     class a : object method f : < g : int; h : int; .. > -> int end
   The method f has type (< g : int; h : int; .. > as 'a) -> int where 'a
   is unbound

この動作はよく知られています。OCAML は がbopen オブジェクト型を持っていると正しく推測します<g:int;h:int;..>が、クラスが型変数を宣言していないと不平を言います。したがって、型変数classA必要なようです。次に、型変数を明示的に導入しました。

バージョン 2:

 # class ['a] classA2 = object
   method f (b:'a) = b#g + b#h
   end ;;
 class ['a] classA2 :
   object constraint 'a = < g : int; h : int; .. > method f : 'a -> int end

これは機能しますが、OCAML が示すように、クラスは型制約を使用して明示的にポリモーフィックになりました。クラス型に型変数が含まれていることも紛らわしいですが、の型値を指定しなく'aても言えます。なぜそれが可能なのですか?let x = new classA2'a

のもう 1 つの欠点classA2は、明示的な型制約(b:'a)に型変数が含まれていることです。結局のところ、b未知の型ではなく、固定されたインターフェイスに準拠する必要があることはわかっています'a。このインターフェースが本当に正しいことを OCAML に検証してもらいたいのです。

バージョン 3 では、最初にインターフェイスclassBをクラス型として宣言し、次にbこの型でなければならないと宣言しました。

 # class type classB = object method g:int method h:int end;;
 class type classB = object method g : int method h : int end
 # class classA3 = object method f (b:classB) = b#g + b#h end;;
 class classA3 : object method f : classB -> int end

classA3これも機能しますが、私の戸惑いは残ります。明示的なポリモーフィズムが必要なくなったのはなぜですか?

質問の要約:

  • が型変数で宣言されているのにnew classA2型を指定せずに使えるのはなぜですか?'aclassA2'a
  • classA3が型制約を受け入れ、(b:classB)バインドされた型変数を必要としないのはなぜですか?
  • と の機能には微妙な違いがclassA2ありclassA3ますか? もしそうなら、どのように違いますか?
4

2 に答える 2

6

これは少し複雑になるので、しっかりと保持してください。まず、classA4バリアントを追加さ​​せてください。これはたまたま実際に必要なものです。

class classA4 = object
  method f : 'a. (#classB as 'a) -> int = fun b -> b#g + b#h
end

クラスclassA2とはすべて微妙classA3classA4異なり、その違いは OCaml が型ポリモーフィズムとオブジェクト ポリモーフィズムをどのように扱うかにあります。と の 2 つのクラスが型を実装しているb1とします。b2classB

オブジェクトのポリモーフィズムに関して言えば、これは、強制構文を使用しb1て型の式を型に強制できることを意味します。この型強制は型情報を破棄する (オブジェクトが type であることがわからなくなる) ため、明示的にする必要があります。classB(new b1 :> classB)b1

ポリモーフィズムに関して言えば、これは制約(または)b1を持つ任意の型変数の代わりに型を使用できることを意味します。これは型情報を破棄しないため (型変数が実際の型に置き換えられるため)、型推論アルゴリズムによって実行されます。#classB< g : int ; h : int ; .. >

のメソッドfclassA3type のパラメーターを期待しますclassB。これは型強制が必須であることを意味します:

let b = new b1 
let a = new classA3
a # f b (* Type error, expected classB, found b1 *)
a # f (b :> classB) (* Ok ! *)

これはまた、(強制する限り) 実装する任意のクラスclassBを使用できることも意味します。

メソッドfclassA2constraint に一致する型のパラメータを期待しますが、#classBOCaml ではそのような型をアンバインドしてはならないことが義務付けられているため、クラス レベルでバインドされます。これは、 のすべてのインスタンスが、実装する単一の任意の型classA2のパラメーターを受け入れることを意味します(その型は型推論されます)。classB

let b1 = new b1 and b2 = new b2
let a  = new classA2 
a # f b1 (* 'a' is type-inferred to be 'b1 classA2' *)
a # f b2 (* Type error, because b1 != b2  *)

classA3が と同等であることに注意することが重要ですclassB classA2。これが、束縛された型変数を必要としない理由であり、また より表現力が厳密に低い理由でもありclassA2ます。

のメソッドfには、クラス レベルではなくメソッド レベルで型変数をバインドする構文classA4を使用して明示的な型が与えられています。'a.これは実際には全称量化子であり、これは « このメソッドは»'aを実装する任意の型に対して呼び出すことができることを意味します#classB:

let b1 = new b1 and b2 = new b2
let a  = new classA4
a # f b1 (* 'a is chosen to be b1 for this call *)
a # f b2 (* 'a is chosen to be b2 for this call *)
于 2012-06-24T12:25:47.097 に答える
4

ビクターのソリューションよりもわずかに単純なソリューションがあります。型をパラメーター化する必要はなくclass2、クラス type を使用するだけclassBです。

class classA2bis = object
  method f (b: classB) = b#g + b#h
end ;;

ビクターのソリューション ( f : 'a . (#classB as 'a) -> int) は、 のスーパータイプである任意の型に対して機能しますclassB。ビクターのソリューションでは、彼が説明しているように、使用されるクラスは呼び出しサイトでインスタンス化されます。暗黙的なポリモーフィックなインスタンス化により、より大きなa#f b任意の型に対して機能します。このソリューションでは、引数は正確なタイプでなければならないため、より大きなタイプの場合は明示的に強制する必要があります: .bclassBclassBba#f (b :> classB)

したがって、どちらのソリューションも複雑さの妥協点が異なります。Victor のソリューションでは、メソッド定義は洗練されたポリモーフィック型を使用し、呼び出しサイトは軽量です。この同等に表現力のあるソリューションを使用すると、定義はより単純になりますが、呼び出しサイトは明示的な強制を使用する必要があります。定義サイトは 1 つだけで、呼び出しサイトは複数あるため、通常は、定義側をより複雑にすることが推奨されます。これは、専門家と思われるライブラリ設計者によって行われます。実際には、どちらのスタイルも出回っているので、両方を理解することが重要です。

ビクターの返信に対するコメントであなたが言っているように見えることに反応する歴史的な発言: 明示的な強制と明示的な普遍的に量化された型変数によるサブタイプは、OCaml への最近の追加ではありません。ChangesOCaml ディストリビューションのファイルを見てください。オブジェクトシステムは OCaml 1.00 (約 1995 年から) にさかのぼり、その頃からサブタイプ (明示的な強制を伴う) が存在し、2002 年にリリースされた OCaml 3.05 にはポリモーフィックメソッドと構造体フィールドが追加されました。

編集:コメントによって促された発言。クラス型ではなくオブジェクト型注釈を使用して、次のように記述することもできます。

class classA2bis = object
  method f (b: < g : int; h : int >) = b#g + b#h
end ;;

classBはあなたの例ですでに定義されているので使用しただけなので、構造注釈を使用してもあまり役に立ちませんでした。このコンテキスト (クラス型は型として使用され、別のクラス型を定義するためではありません) では、この 2 つは同等です。それらは、パラメータとして取られるオブジェクトの実装について何も明らかにしません。OCaml のオブジェクト システムが構造型付けを使用していることを考えると、適切な型を持つこれら 2 つのメソッドを持つ任意のオブジェクトは、この型注釈に適合すると主張できます (潜在的に明示的なサブタイピング ステップを介して)。自分のクラス定義への参照がまったくない状態で、他の誰かによって定義された可能性があります。b

ocaml オブジェクト システムでは、オブジェクト型とクラス型の間に比較的微妙な違いがありますが、それについてはあまり知りません。オブジェクト指向プログラミングは使用していません。必要に応じて、リファレンス マニュアルまたはU3 ブックで詳細を学習できます。

編集 2#classB as 'a :とは一般的にclassB同等に表現できないことに注意してください。最初のより複雑な定式化は、タイプの異なる出現間の共有を表現する場合に役立ちます。たとえば、は とは大きく異なるタイプです。戻り値の型に関する厳密により多くの情報を保持するため、厳密に一般的です。ただし、あなたの場合、クラスタイプの出現は1つしかないため、両方のタイプが同等に表現されているため、潜在的な共有はありません。'a . (#foo as 'a) -> 'afoo -> foo

于 2012-06-24T13:48:23.610 に答える