OCaml 5.2 refman
https://ocaml.org/manual/5.2/index.html
W.I.P
☰
OCaml システム リリース 5.2
2024 年 2 月
このマニュアルは、PDF、プレーン テキスト、HTML ファイルのバンドル、およびEmacs Info ファイルのバンドル でもご利用いただけます。
OCaml マニュアル
目次
序文
キーワード索引
OCaml API
OCaml コンパイラ API
目次
序文
I. OCaml の紹介
1. コア言語
2. モジュール システム
3. OCaml のオブジェクト
4. ラベル付き引数
5. 多態的バリアント
6. 多態性とその制限
7. 一般化された代数的データ型
8. クラスとモジュールを使用した高度な例
9. 並列プログラミング
10. メモリ モデル: 難しい部分
II. OCaml 言語
11. OCaml 言語
12. 言語拡張
III. OCaml ツール
13.バッチ コンパイル (ocamlc)
14. トップレベル システムまたは REPL (ocaml)
15. ランタイム システム (ocamlrun)
16. ネイティブ コードのコンパイル (ocamlopt)
17. レクサーおよびパーサー ジェネレーター (ocamllex、ocamlyacc)
18. 依存関係ジェネレーター (ocamldep)
19. ドキュメント ジェネレーター (ocamldoc)
20. デバッガー (ocamldebug)
21. プロファイリング (ocamlprof)
22. C と OCaml のインターフェイス
23. Flambda による最適化
24. afl-fuzz によるファジング
25. ランタイム イベントによる実行時トレース
26. “Tail Modulo Constructor” プログラム変換
27. ThreadSanitizer によるデータ競合の実行時検出
IV. OCaml ライブラリ
28. コア ライブラリ
29. 標準ライブラリ
30. コンパイラ フロントエンド
31. unix ライブラリ: Unix システム コール
32. str ライブラリ: 正規表現と文字列処理
33.ランタイム イベント ライブラリ
34. スレッド ライブラリ
35. dynlink ライブラリ: オブジェクト ファイルの動的読み込みとリンク
36.最近削除または移動されたライブラリ (Graphics、Bigarray、Num、LablTk)
V. インデックス
モジュールのインデックス
モジュール タイプのインデックス
タイプのインデックス
例外のインデックス
値のインデックス
キーワードのインデックス
Xavier Leroy、
Damien Doligez、Alain Frisch、Jacques Garrigue、Didier Rémy、ジェローム・ヴイヨン Copyright © 2024 Institut National de Recherche en Informatique et en Automatique
☰
☰OCaml の紹介
第 3 章 OCaml のオブジェクト
OCaml のオブジェクト
[13 関数オブジェクト](objectexamples.html#s%3A functional-objects)
17 人の友達
この章では、OCaml のオブジェクト指向機能の概要を説明します。
OCaml のオブジェクト、クラス、型の関係は、Java や C++ などの主流のオブジェクト指向言語とは異なるため、類似のキーワードが同じ意味を持つとは想定しないでください。OCaml では、これらの言語に比べてオブジェクト指向機能の使用頻度ははるかに低くなっています。OCaml には、モジュールやファンクタなど、より適切な代替手段があります。実際、多くの OCaml プログラムでは、オブジェクトはまったく使用されていません。
`# class point = object val mutable x = 0 method get_x = x method move d = x <- x + d end;;`
1 クラスとオブジェクト
以下のクラス point は、1 つのインスタンス変数 x と 2 つのメソッド get_x および move を定義します。インスタンス変数の初期値は 0 です。変数 x は可変として宣言されているため、メソッド move で値を変更できます。
クラス point: object val mutable x: int メソッド get_x: int メソッド move: int -> unit end
`# let p = new point;;`
ここで、ポイント クラスのインスタンスである新しいポイント p を作成します。
val p : point = <obj>
`# p#get_x;;`
`- : int = 0`
p の型は point であることに注意してください。これは、上記のクラス定義によって自動的に定義される略語です。これは、オブジェクト型 <get_x : int; move : int -> unit> を表し、クラス point のメソッドとその型をリストします。
ここで、p のいくつかのメソッドを呼び出します。
`# p#move 3;;`
`- : unit = ()`
`# p#get_x;;`
`- : int = 3`
クラスの本体の評価は、オブジェクトの作成時にのみ行われます。したがって、次の例では、インスタンス変数 x は 2 つの異なるオブジェクトに対して異なる値に初期化されます。
`# let x0 = ref 0;;`
`# class point = object val mutable x = incr x0; !x0 method get_x = x method move d = x <- x + d end;;`
val x0 : int ref = {contents = 0}
クラス point : object val mutable x : int メソッド get_x : int メソッド move : int -> unit 終了
`# new point#get_x;;`
`- : int = 1`
`# new point#get_x;;`
`- : int = 2`
クラス point は、x 座標の初期値に対して抽象化することもできます。
`# 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;;`
クラス point: int -> object val mutable x: int メソッド get_x: int メソッド move: int -> unit end
関数定義と同様に、上記の定義は次のように省略できます。
`# new point;;`
`- : int -> point = <fun>`
クラス point: int -> object val mutable x: int メソッド get_x: int メソッド move: int -> unit end
クラス point のインスタンスは、ポイント オブジェクトを作成するために初期パラメータを必要とする関数になりました。
`# let p = new point 7;;`
`# 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;;`
val p : point = <obj>
パラメータ x_init は、もちろん、メソッドを含む定義の本体全体で表示されます。たとえば、以下のクラスのメソッド get_offset は、オブジェクトの初期位置に対する相対的な位置を返します。
`# 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: int -> object val mutable x: int メソッド get_offset: int メソッド get_x: int メソッド move: int -> unit end
クラスのオブジェクト本体を定義する前に、式を評価してバインドすることができます。これは不変条件を強制するのに便利です。たとえば、次のように、ポイントをグリッド上の最も近いポイントに自動的に調整できます。
クラス adapted_point: int -> object val mutable x: int メソッド get_offset: int メソッド get_x: int メソッド move: int -> unit end
`# class adjusted_point x_init = point ((x_init / 10) * 10);;`
(x_init 座標がグリッド上にない場合は例外を発生させることもできます。) 実際、原点の値を使用してクラス point の定義を呼び出すことで、ここで同じ効果を得ることができます。
class adapted_point : int -> point
`# let new_adjusted_point x_init = new point ((x_init / 10) * 10);;`
別の解決策としては、調整を特別な割り当て関数で定義することです:
val new_adjusted_point : int -> point = <fun>
ただし、調整のコードはクラスの定義の一部であり、継承されるため、通常は前者のパターンの方が適切です。
この機能は、他の言語にあるようなクラス コンストラクターを提供します。この方法で複数のコンストラクターを定義して、同じクラスのオブジェクトを異なる初期化パターンで構築することができます。別の方法としては、以下のセクション 3.4 で説明するように、初期化子を使用する方法があります。
2 即時オブジェクト
オブジェクトを作成するもう 1 つのより直接的な方法は、クラスを経由せずに作成することです。
構文はクラス式とまったく同じですが、結果はクラスではなく単一のオブジェクトになります。このセクションの残りの部分で説明するすべての構成要素は、即時オブジェクトにも適用されます。
`# let p = object val mutable x = 0 method get_x = x method move d = x <- x + d end;;`
`# p#get_x;;`
`- : int = 0`
`# p#move 3;;`
val p : < get_x : int; move : int -> unit > = <obj>
`- : 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>
クラスと比較すると、即時オブジェクトには 2 つの弱点があります。つまり、型が省略されず、継承できないことです。しかし、この 2 つの弱点は、状況によっては利点になることがあります。これについては、セクション 3.3 と 3.10 で説明します。
`# class printable_point x_init = object (s) val mutable x = x_init method get_x = x method move d = x <- x + d method print = print_int s#get_x end;;`
3 self への参照
メソッドまたは初期化子は、self (つまり、現在のオブジェクト) のメソッドを呼び出すことができます。そのためには、self を明示的にバインドする必要があります。ここでは変数 s にバインドします (s は任意の識別子にすることができますが、self という名前を選択することがよくあります)。
class printable_point : int -> object val mutable x : int method get_x : int method move : int -> unit method print : unit end
`# let p = new printable_point 7;;`
`# p#print;;`
`- : unit = ()`
val p : 印刷可能なポイント = <obj>
`# let ints = ref [];;`
動的に、変数 s はメソッドの呼び出し時にバインドされます。特に、クラス printable_point が継承されると、変数 s はサブクラスのオブジェクトに正しくバインドされます。
self の一般的な問題は、その型がサブクラスで拡張される可能性があるため、事前に修正できないことです。簡単な例を次に示します。
val ints : '_weak1 list ref = {contents = []}
`# class my_int = object (self) method n = 1 method register = ints := self :: !ints end ;;`
エラー: この式の型は < n : int; register : 'a; .. > ですが、式は 'weak1 型であるはずです。自己型はクラスをエスケープできません
エラー メッセージの最初の 2 行は無視できます。重要なのは最後の行です。自己を外部参照に入れると、継承によって拡張できなくなります。この問題の回避策については、セクション 3.12 で説明します。ただし、即時オブジェクトは拡張できないため、即時オブジェクトではこの問題は発生しないことに注意してください。
`# let my_int = object (self) method n = 1 method register = ints := self :: !ints end;;`
val my_int : < n : int; レジスタ : ユニット > = <obj>
`# 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;;`
4 初期化子
クラス定義内の let バインディングは、オブジェクトが構築される前に評価されます。オブジェクトが構築された直後に式を評価することもできます。このようなコードは、初期化子と呼ばれる匿名の隠しメソッドとして記述されます。したがって、self とインスタンス変数にアクセスできます。
class printable_point : int -> object val mutable x : int method get_x : int method move : int -> unit method print : unit end
`# let p = new printable_point 17;;`
10 の新しいポイント val p : printable_point = <obj>
初期化子はオーバーライドできません。逆に、すべての初期化子は順番に評価されます。初期化子は不変条件を強制するのに特に便利です。別の例はセクション 8.1 にあります。
`# 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;;`
5 仮想メソッド
キーワード virtual を使用すると、メソッドを実際に定義せずに宣言できます。このメソッドは、後でサブクラスで提供されます。仮想メソッドを含むクラスには、virtual フラグを設定する必要があり、インスタンス化できません (つまり、このクラスのオブジェクトは作成できません)。それでも、型の省略形が定義されます (仮想メソッドを他のメソッドとして扱います)。
class virtual abstract_point : int -> object method get_offset : int method virtual get_x : int 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;;`
class point : int -> object val mutable x : int method get_offset : int method get_x : int method move : int -> unit end
インスタンス変数は、メソッドと同じ効果を持つ仮想として宣言することもできます。
`# 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;;`
クラス仮想 abstract_point2 : オブジェクト val 可変 仮想 x : int メソッド move : int -> unit 終了
クラス point2 : int -> オブジェクト val 可変 x : int メソッド get_offset : int メソッド move : int -> unit 終了
`# 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;;`
6 プライベート メソッド
プライベート メソッドは、オブジェクト インターフェイスに表示されないメソッドです。同じオブジェクトの他のメソッドからのみ呼び出すことができます。
class restrict_point : int -> object val mutable x : int method bump : unit method get_x : int method private move : int -> unit end
`# let p = new restricted_point 0;;`
`# p#move 10 ;;`
val p : restrict_point = <obj>
エラー: この式には、restricted_point 型があります。move メソッドがありません
`# p#bump;;`
`- : unit = ()`
これは、同じクラスの他のオブジェクトから呼び出すことができる Java や C++ のプライベート メソッドやプロテクト メソッドと同じではないことに注意してください。これは、OCaml の型とクラスの独立性から直接生じる結果です。2 つの無関係なクラスが同じ型のオブジェクトを生成する可能性があり、型レベルでオブジェクトが特定のクラスからのものであることを確認する方法はありません。ただし、フレンド メソッドの可能なエンコードについては、セクション 3.17 で説明します。
プライベート メソッドは、以下で説明するように、シグネチャ マッチングによって非表示にされない限り、継承されます (サブクラスではデフォルトで表示されます)。
`# class point_again x = object (self) inherit restricted_point x method virtual move : _ end;;`
プライベート メソッドはサブクラスでパブリックにすることができます。
クラス point_again: int -> object val mutable x: int メソッド bump: unit メソッド get_x: int メソッド move: int -> unit end
`# 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
`# class point_again x = object inherit restricted_point x as super method move = super#move end;;`
self の型に対する制約は、public な move メソッドを要求することであり、これは private をオーバーライドするのに十分です。
サブクラスでは private メソッドを private のままにしておくべきだと考える人もいるかもしれません。しかし、メソッドはサブクラスで可視であるため、そのコードを選択して、そのコードを実行する同じ名前のメソッドを定義することが常に可能です。そのため、さらに別の (より重い) 解決策は次のようになります。
class point_again : int -> object val mutable x : int method bump : unit method get_x : int method move : int -> unit end
もちろん、プライベート メソッドも仮想メソッドにすることができます。その場合、キーワードは次の順序で出現する必要があります: method private virtual。
`# class type restricted_point_type = object method get_x : int method bump : unit end;;`
7 クラス インターフェース
クラス インターフェースはクラス定義から推論されます。直接定義して、クラスの型を制限するために使用することもできます。クラス宣言と同様に、新しい型の省略形も定義します。
class type restrict_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' = (restricted_point : int -> restricted_point_type);;`
クラス restrict_point' : int -> restrict_point_type
または、同等:
クラス restrict_point' : int -> restrict_point_type
`# module type POINT = sig class restricted_point' : int -> object method get_x : int method bump : unit end end;;`
クラスのインターフェースはモジュール シグネチャで指定することもでき、モジュールの推論されたシグネチャを制限するために使用できます。
モジュール タイプ POINT = sig クラス restrict_point' : int -> object メソッド bump : unit メソッド get_x : int end end
`# module Point : POINT = struct class restricted_point' = restricted_point end;;`
モジュールポイント: POINT
`# class colored_point x (c : string) = object inherit point x val c = c method color = c end;;`
8 継承
継承を説明するために、ポイントのクラスから継承する色付きポイントのクラスを定義します。このクラスには、クラス point のすべてのインスタンス変数とすべてのメソッドに加えて、新しいインスタンス変数 c と新しいメソッド color があります。
class coloured_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";;`
`# p'#get_x, p'#color;;`
`- : int * string = (5, "red")`
val p' : 有色点 = <obj>
`# let get_succ_x p = p#get_x + 1;;`
`# get_succ_x p + get_succ_x p';;`
点と色付きの点の型は互換性がありません。点にはメソッド color がないためです。ただし、以下の関数 get_x は、このメソッドを持つ任意のオブジェクト p (および型内の省略記号で表されるその他のメソッド) にメソッド get_x を適用する汎用関数です。したがって、この関数は点と色付きの点の両方に適用されます。
val get_succ_x : < get_x : int; .. > -> int = <fun>
`- : int = 8`
`# 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>
`# 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;;`
9 多重継承
多重継承が許可されます。メソッドの最後の定義のみが保持されます。親クラスで表示されていたメソッドのサブクラスでの再定義は、親クラスの定義をオーバーライドします。メソッドの以前の定義は、関連する祖先をバインドすることで再利用できます。以下では、super は祖先の printable_point にバインドされています。名前 super は、super#print のように、スーパークラスのメソッドを呼び出すためにのみ使用できる疑似値識別子です。
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";;`
`# p'#print;;`
`- : unit = ()`
(10, 赤) の新しい点 val p' : printable_colored_point = <obj>
`# object method! m = () end;;`
親クラスで非表示にされていたプライベート メソッドは表示されなくなり、オーバーライドされません。初期化子はプライベート メソッドとして扱われるため、クラス階層のすべての初期化子は、導入された順に評価されます。
わかりやすくするために、メソッド キーワードに感嘆符 ! を付けて、メソッド print が別の定義をオーバーライドしていることを明示的にマークしていることに注意してください。メソッド print が printable_point の print メソッドをオーバーライドしていない場合、コンパイラはエラーを生成します:
エラー: メソッド m には以前の定義がありません
`# class another_printable_colored_point y c c' = object (self) inherit printable_point y inherit! printable_colored_point y c val! c = c' end;;`
この明示的なオーバーライド注釈は、val と inherit にも適用されます:
class another_printable_colored_point : int -> string -> string -> object val c : string val mutable x : int method color : string method get_x : int method move : int -> unit method print : unit end
`# class oref x_init = object val mutable x = x_init method get = x method set y = x <- y end;;`
10 パラメータ化されたクラス
参照セルはオブジェクトとして実装できます。単純な定義は型チェックに失敗します:
エラー: この型では一部の型変数がバインドされていません: class oref: 'a -> object val mutable x: 'a method get: 'a method set: 'a -> unit end メソッド get の型は 'a ですが、'a はバインドされていません
`# class oref (x_init:int) = object val mutable x = x_init method get = x method set y = x <- y end;;`
その理由は、メソッドの少なくとも 1 つに多態型 (ここでは参照セルに格納されている値の型) があるため、クラスをパラメトリックにするか、メソッドの型を単態型に制限する必要があるためです。クラスの単態インスタンスは次のように定義できます。
class oref: int -> object val mutable x: int method get: int method set: int -> unit end
`# let new_oref x_init = object val mutable x = x_init method get = x method set y = x <- y end;;`
即時オブジェクトはクラス型を定義しないため、このような制限はありません。
val new_oref : 'a -> < get : 'a; set : 'a -> unit > = <fun>
一方、ポリモーフィック参照のクラスでは、宣言に型パラメータを明示的にリストする必要があります。クラス型パラメータは、[ と ] の間にリストされます。型パラメータは、クラス本体のどこかで型制約によってバインドされる必要もあります。
`# class ['a] oref x_init = object val mutable x = (x_init : 'a) method get = x method set y = x <- y end;;`
`# let r = new oref 1 in r#set 2; (r#get);;`
`- : int = 2`
クラス ['a] oref : 'a -> object val mutable x : 'a メソッド get : 'a メソッド set : 'a -> unit 終了
`# class ['a] oref_succ (x_init:'a) = object val mutable x = x_init + 1 method get = x method set y = x <- y end;;`
宣言内の型パラメータは、実際にはクラス定義の本体で制約されることがあります。クラス型では、型パラメータの実際の値が制約句に表示されます。
class ['a] oref_succ : 'a -> object 制約 'a = int val mutable x : int メソッド get : int メソッド 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 制約 'a = < move : int -> unit; .. > val mutable center : 'a メソッド center : 'a メソッド move : int -> unit メソッド set_center : 'a -> unit end
クラス定義の制約句を使用した、circle の別の定義を以下に示します。制約句で以下で使用される型 #point は、クラス point の定義によって生成された省略形です。この省略形は、クラス point のサブクラスに属する任意のオブジェクトの型と統合されます。実際には、< get_x : int; move : int -> unit; .. > に展開されます。これにより、circle の次の別の定義が生まれます。この定義では、center にメソッド get_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] colored_circle c = object constraint 'a = #colored_point inherit ['a] circle c method color = center#color end;;`
class ['a] circle : 'a -> object 制約 'a = #point val mutable center : 'a メソッド center : 'a メソッド move : int -> unit メソッド set_center : 'a -> unit end
colored_circle クラスは、circle クラスの特殊バージョンであり、center の型を #colored_point と統合する必要があり、メソッド color が追加されています。パラメータ化されたクラスを特殊化する場合、型パラメータのインスタンスを常に明示的に指定する必要があることに注意してください。これも [ と ] の間に記述されます。
クラス ['a] coloured_circle : 'a -> object 制約 'a = #colored_point val mutable center : 'a メソッド center : 'a メソッド color : string メソッド move : int -> unit メソッド set_center : 'a -> unit 終了
11 ポリモーフィック メソッド
パラメータ化されたクラスは内容がポリモーフィックである可能性がありますが、メソッドの使用のポリモーフィズムを可能にするには十分ではありません。
典型的な例としては、イテレータの定義があります。
`# List.fold_left;;`
`- : ('acc -> 'a -> 'acc) -> 'acc -> 'a list -> 'acc = <fun>`
`# 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 メソッド empty : bool メソッド fold : ('a -> int -> 'a) -> 'a -> 'a end
一見すると、多態的なイテレータがあるように見えますが、実際には機能しません。
`# let l = new intlist [1; 2; 3];;`
`# l#fold (fun x y -> x+y) 0;;`
`- : int = 6`
val l : '_weak2 intlist = <obj>
`# l;;`
`- : int intlist = <obj>`
`# l#fold (fun s x -> s ^ Int.to_string x ^ " ") "" ;;`
エラー: この式は int 型ですが、式は string 型であるはずです
最初の合計での使用例が示すように、反復子は機能します。ただし、オブジェクト自体は多態的ではないため (コンストラクタのみが多態的)、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;;`
ここでの問題は、量化が間違った場所にあることです。多態性を持たせたいのはクラスではなく、fold メソッドです。これは、メソッド定義で明示的に多態性型を与えることで実現できます。
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;;`
`- : int = 6`
`# l#fold (fun s x -> s ^ Int.to_string x ^ " ") "";;`
val l : intlist = <obj>
`- : string = "1 2 3 "`
`# class intlist_rev l = object inherit intlist l method! fold f accu = List.fold_left f accu (List.rev l) end;;`
コンパイラによって表示されるクラス型を見るとわかるように、多態的メソッド型はクラス定義 (メソッド名の直後に出現) で完全に明示的に指定する必要がありますが、量化された型変数はクラス記述で暗黙のままにすることができます。型を明示的に指定する必要があるのはなぜでしょうか。問題は、(int -> int -> int) -> int -> int も fold の有効な型であり、指定した多態的型と互換性がないことです (自動インスタンス化はトップレベルの型変数に対してのみ機能し、内部量指定子に対しては機能しません。内部量指定子の場合は決定不能な問題になります)。そのため、コンパイラはこれら 2 つの型のいずれかを選択できず、支援を受ける必要があります。
ただし、継承または self の型制約によって型が既にわかっている場合は、クラス定義で型を完全に省略できます。メソッドのオーバーライドの例を次に示します。
`# 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) 慣用句に注目してください。これにより、このオブジェクトがインターフェイス イテレータを実装することが保証されます。
`# let sum lst = lst#fold (fun x y -> x+y) 0;;`
`# sum l ;;`
ポリモーフィック メソッドは通常のメソッドとまったく同じ方法で呼び出されますが、型推論の制限事項に注意してください。つまり、ポリモーフィック メソッドは、呼び出しサイトでその型がわかっている場合にのみ呼び出すことができます。そうでない場合、メソッドはモノモーフィックであると想定され、互換性のない型が与えられます。
val sum : < fold : (int -> int -> int) -> int -> 'a; .. > -> 'a = <fun>
`# let sum (lst : _ #iterator) = lst#fold (fun x y -> x+y) 0;;`
エラー: この式は intlist 型ですが、式は < fold : (int -> int -> int) -> int -> 'b; .. > 型が期待されていました。メソッド fold の型は 'a です。('a -> int -> 'a) -> 'a -> 'a ですが、期待されたメソッド型は (int -> int -> int) -> int -> 'b でした
回避策は簡単です。パラメータに型制約を設定する必要があります。
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;;`
ポリモーフィック メソッドのもう 1 つの用途は、メソッド引数で何らかの暗黙的なサブタイプ化を可能にすることです。セクション 3.8 で、一部の関数が引数のクラスでポリモーフィックになる可能性があることをすでに説明しました。これはメソッドにも拡張できます。
class type point0 = object method get_x : int 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 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
ここで、#point0 の拡張可能な部分を定量化するために使用する必要がある特別な構文 (#point0 as 'a) に注意してください。変数バインダーについては、クラス仕様では省略できます。オブジェクト フィールド内でポリモーフィズムが必要な場合は、個別に定量化する必要があります。
`# 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 メソッド m1 : < n1 : 'b. 'b -> 'b; .. > -> bool * string メソッド m2 : < n2 : 'b -> bool; .. > -> 'b -> bool end
メソッド m1 では、o は少なくともメソッド n1 を持つオブジェクトでなければならず、それ自体が多態的である必要があります。メソッド m2 では、n2 と x の引数は同じ型でなければならず、これは 'a と同じレベルで量化されます。
12 強制型変換の使用
サブタイプ化は暗黙的ではありません。ただし、サブタイプ化を実行する方法は 2 つあります。最も一般的な構成は完全に明示的です。つまり、型強制型のドメインと共ドメインの両方を指定する必要があります。
ポイントと色付きポイントには互換性のない型があることを見てきました。たとえば、同じリストに混在させることはできません。ただし、色付きポイントをポイントに強制型変換して、そのカラー メソッドを隠すことができます。
`# let colored_point_to_point cp = (cp : colored_point :> point);;`
`# let p = new point 3 and q = new colored_point 4 "blue";;`
val coloured_point_to_point : coloured_point -> point = <fun>
val p : point = <obj> val q : coloured_point = <obj>
`# let l = [p; (colored_point_to_point q)];;`
`# (p : point :> colored_point);;`
val l : point list = [<obj>; <obj>]
t 型のオブジェクトは、t が t' のサブタイプである場合にのみ、t' 型のオブジェクトとして見ることができます。たとえば、点は色付きの点として見ることはできません。
エラー: 型 point = < get_offset : int; get_x : int; move : int -> unit > は、colored_point = < color : string; get_offset : int; get_x : int; move : int -> unit > のサブタイプではありません。最初のオブジェクト型にはメソッド color がありません
実際、実行時チェックなしの縮小型変換は安全ではありません。実行時の型チェックでは例外が発生する可能性があり、実行時に型情報の存在が必要になりますが、OCaml システムではそうではありません。これらの理由から、言語ではそのような操作は利用できません。
サブタイプと継承は関連がないことに注意してください。継承はクラス間の構文関係ですが、サブタイプは型間の意味関係です。たとえば、色付きの点のクラスは、点のクラスから継承せずに直接定義できます。色付きの点の型は変更されず、点のサブタイプのままです。
`# let to_point cp = (cp :> point);;`
強制のドメインは省略できる場合がよくあります。たとえば、次のように定義できます。
val to_point : #point -> point = <fun>
この場合、関数 coloured_point_to_point は関数 to_point のインスタンスです。ただし、これは常に当てはまるわけではありません。完全に明示的な強制の方が正確であり、避けられない場合もあります。たとえば、次のクラスを考えてみましょう。
`# class c0 = object method m = {< >} method n = 0 end;;`
クラス c0 : オブジェクト ('a) メソッド m : 'a メソッド n : int 終了
オブジェクト型 c0 は、<m : 'a; n : int> as 'a の省略形です。次に、型宣言について考えてみましょう。
`# class type c1 = object method m : c1 end;;`
`# fun (x:c0) -> (x : c0 :> c1);;`
クラス タイプ c1 = オブジェクト メソッド m : c1 終了
オブジェクト タイプ c1 は、タイプ <m : 'a> の 'a としての省略形です。タイプ c0 のオブジェクトからタイプ c1 のオブジェクトへの強制変換は正しいです:
`- : c0 -> c1 = <fun>`
`# class type c2 = object ('a) method m : 'a end;;`
ただし、強制のドメインを常に省略できるわけではありません。その場合、解決策は明示的な形式を使用することです。場合によっては、クラス型定義の変更によっても問題が解決されることがあります
class type c2 = object ('a) method m : 'a end
`# fun (x:c0) -> (x :> c2);;`
`- : c0 -> c2 = <fun>`
`# let to_c1 x = (x :> c1);;`
クラス型 c1 と c2 は異なりますが、オブジェクト型 c1 と c2 は両方とも同じオブジェクト型 (同じメソッド名と型) に展開されます。ただし、強制のドメインが暗黙のままで、その共ドメインが既知のクラス型の省略形である場合、強制関数を導出するためにオブジェクト型ではなくクラス型が使用されます。これにより、サブクラスからスーパークラスに強制するときに、ほとんどの場合、ドメインを暗黙のままにすることができます。強制の型は常に次のように表示されます:
val to_c1 : < m : #c1; .. > -> c1 = <fun>
`# let to_c2 x = (x :> c2);;`
val to_c2 : #c2 -> c2 = <fun>
これら 2 つの強制の違いに注意してください。to_c2 の場合、型 #c2 = < m : 'a; .. > as 'a は多態的に再帰的です (c2 のクラス型内の明示的な再帰による)。したがって、この強制をクラス c0 のオブジェクトに適用すると成功します。一方、最初のケースでは、c1 は 2 回展開および展開されただけで、再帰は導入されずに < m : < m : c1; .. >; .. > (#c1 = < m : c1; .. > を思い出してください) が取得されました。to_c2 の型は #c2 -> c2 ですが、to_c1 の型は #c1 -> c1 よりも一般的であることにも注意してください。これは常に当てはまるわけではありません。セクション 3.16 で説明されているように、#c のインスタンスの一部が c のサブタイプではないクラス型が存在するためです。ただし、パラメーターのないクラスの場合、強制変換 (_ :> c) は常に (_ : #c :> c) よりも一般的です。
`# fun x -> (x :> 'a);;`
`- : 'a -> 'a = <fun>`
クラス c の定義中にクラス c への強制型変換を定義しようとすると、よくある問題が発生することがあります。この問題は、型の省略形がまだ完全に定義されていないため、そのサブタイプが明確にわかっていないことが原因です。次に、(_ :> c) または (_ : #c :> c) の強制型変換が、次のように恒等関数として扱われます。
その結果、次の例のように強制型変換が self に適用されると、self の型は閉じた型 c と統合されます (閉じたオブジェクト型は省略記号のないオブジェクト型です)。これにより、self の型が閉じられることが制約されるため、拒否されます。実際、self の型は閉じることはできません。これにより、クラスのそれ以上の拡張が妨げられます。したがって、この型を別の型と統合すると閉じたオブジェクト型になる場合は、型エラーが生成されます。
`# class c = object method m = 1 end and d = object (self) inherit c method n = 2 method as_c = (self :> c) end;;`
`# class c = object (self) method m = (self :> c) end;;`
エラー: この式は c = < m : int > 型に強制変換できません。型は < as_c : c; m : int; n : int; .. > ですが、ここでは c 型で使用されています。自己型はクラスを回避できません
ただし、この問題の最も一般的なインスタンスである、自己を現在のクラスに強制変換することは、型チェッカーによって特殊なケースとして検出され、適切に型付けされます。
`# let all_c = ref [];;`
class c : object method m : c end
これにより、クラスまたはそのサブクラスに属するすべてのオブジェクトのリストを保持する次のイディオムが可能になります:
val all_c : '_weak3 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 ;;`
`# let lookup_c obj = lookup_obj obj !all_c;;`
val lookup_obj : < .. > -> (< .. > as 'a) list -> 'a = <fun>
val lookup_c : < .. > -> < m : int > = <fun>
`# class type c' = object method m : int end;;`
ここで見られる型 < m : int > は、参照の使用による c の単なる拡張です。c 型のオブジェクトを取得することに成功しました。
前述の強制型変換の問題は、クラス型を使用して最初に省略形を定義することで回避できることがよくあります。
`# class c : c' = object method m = 1 end and d = object (self) inherit c method n = 2 method as_c = (self :> c') end;;`
クラス タイプ c' = オブジェクト メソッド m: int 終了
クラス c: c' および d: オブジェクト メソッド as_c: c' メソッド m: int メソッド n: int 終了
仮想クラスを使用することもできます。このクラスから継承すると、c のすべてのメソッドが c' のメソッドと同じ型に強制されます。
`# class virtual c' = object method virtual m : int end;;`
`# class c = object (self) inherit c' method m = 1 end;;`
クラス仮想 c' : オブジェクト メソッド仮想 m : int 終了
クラス c : オブジェクト メソッド m : int 終了
`# type c' = <m : int>;;`
型省略形を直接定義することも考えられます:
ただし、省略形 #c' は同様の方法で直接定義できません。クラスまたはクラス型定義によってのみ定義できます。これは、# 省略形が暗黙の匿名変数 .. を持ち、明示的に名前を付けることができないからです。それに近づくと次のようになります:
`# type 'a c'_class = 'a constraint 'a = < m : int; .. >;;`
開いているオブジェクトのタイプをキャプチャする追加の型変数を使用します。
`# class functional_point y = object val x = y method get_x = x method move d = {< x = x + d >} method move_to x = {< x >} end;;`
13 関数オブジェクト
インスタンス変数への割り当てを行わないクラス point のバージョンを記述することも可能です。オーバーライド構造 {< ... >} は「self」(つまり現在のオブジェクト) のコピーを返し、インスタンス変数の値を変更する可能性があります。
class functional_point : int -> object ('a) val x : int method get_x : int method move : int -> 'a method move_to : int -> 'a end
`# let p = new functional_point 7;;`
`# p#get_x;;`
`- : int = 7`
val p : 機能ポイント = <obj>
`# (p#move 3)#get_x;;`
`- : int = 10`
`# (p#move_to 15)#get_x;;`
`- : int = 15`
`# p#get_x;;`
`- : int = 7`
`# class bad_functional_point y = object val x = y method get_x = x method move d = new bad_functional_point (x+d) method move_to x = new bad_functional_point x end;;`
レコードと同様に、形式 {< x >} は {< x = x >} の省略バージョンであり、インスタンス変数名の繰り返しを回避します。型省略形 functional_point は再帰的であることに注意してください。これは functional_point のクラス型で確認できます。self の型は 'a であり、'a はメソッド move の型内に出現します。
上記の functional_point の定義は、次の定義と同等ではありません。
class bad_ functional_point : int -> object val x : int method get_x : int method move : int -> bad_ functional_point method move_to : int -> bad_ functional_point end
どちらのクラスのオブジェクトも同じように動作しますが、サブクラスのオブジェクトは異なります。bad_ functional_point のサブクラスでは、メソッド move は親クラスのオブジェクトを返し続けます。逆に、 functional_point のサブクラスでは、メソッド move はサブクラスのオブジェクトを返します。
関数更新は、セクション 8.2.1 に示されているように、バイナリ メソッドと組み合わせて使用されることがよくあります。
14 オブジェクトのクローン作成
関数型か命令型かに関係なく、オブジェクトをクローンすることもできます。ライブラリ関数 Oo.copy は、オブジェクトの浅いコピーを作成します。つまり、引数と同じメソッドとインスタンス変数を持つ新しいオブジェクトを返します。インスタンス変数はコピーされますが、その内容は共有されます。コピーのインスタンス変数に新しい値を割り当てても (メソッド呼び出しを使用)、元のインスタンス変数には影響しません。逆の場合も同様です。より深い割り当て (たとえば、インスタンス変数が参照セルの場合) は、もちろん、元のインスタンスとコピーの両方に影響します。
Oo.copy の型は次のとおりです。
`# Oo.copy;;`
`- : (< .. > as 'a) -> 'a = <fun>`
`# let p = new point 5;;`
その型の as キーワードは、型変数 'a をオブジェクト型 < .. > にバインドします。したがって、Oo.copy は任意のメソッド (省略記号で表されます) を持つオブジェクトを受け取り、同じ型のオブジェクトを返します。Oo.copy の型は、各省略記号が異なるメソッド セットを表すため、型 < .. > -> < .. > とは異なります。省略記号は実際には型変数として動作します。
val p : point = <obj>
`# let q = Oo.copy p;;`
`# q#move 7; (p#get_x, q#get_x);;`
`- : int * int = (5, 12)`
val q : ポイント = <obj>
`# let q = Oo.copy p;;`
実際、Oo.copy p は、p のクラスに本体 {< >} を持つパブリック メソッド copy が定義されていると仮定すると、p#copy として動作します。
オブジェクトは、汎用比較関数 = および <> を使用して比較できます。2 つのオブジェクトが等しいのは、物理的に等しい場合のみです。特に、オブジェクトとそのコピーは等しくありません。
val q : point = <obj>
`# p = q, p = p;;`
`- : bool * bool = (false, true)`
(<、<=、...) などの他の一般的な比較もオブジェクトで使用できます。関係 < は、オブジェクトに対して指定されていないが厳密な順序を定義します。2 つのオブジェクト間の順序関係は、2 つのオブジェクトが作成されると永続的に固定され、フィールドの変更による影響を受けません。
クローン作成とオーバーライドには、空でない共通部分があります。オブジェクト内で使用され、フィールドをオーバーライドしない場合は、これらは互換性があります。
`# class copy = object method copy = {< >} end;;`
`# class copy = object (self) method copy = Oo.copy self end;;`
クラス コピー: オブジェクト ('a) メソッド コピー: 'a 終了
クラス コピー: オブジェクト ('a) メソッド コピー: 'a 終了
`# 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;;`
実際にフィールドをオーバーライドするには override のみを使用でき、外部で使用できるのは Oo.copy プリミティブのみです。
クローン作成は、オブジェクトの状態を保存および復元するための機能を提供するためにも使用できます。
`# class ['a] backup_ref x = object inherit ['a] oref x inherit backup end;;`
class backup : object ('a) val mutable copy : 'a option method restore : 'a method save : unit end
上記の定義では、1 つのレベルのみがバックアップされます。バックアップ機能は、多重継承を使用して任意のクラスに追加できます。
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);;`
`# 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]`
val get : (< get : 'b; restore : 'a; .. > as 'a) -> int -> 'b = <fun>
`# 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 ['a] backup_ref x = object inherit ['a] oref x inherit backup end;;`
すべてのコピーを保持するバックアップのバリアントを定義できます。(すべてのコピーを手動で消去するためのメソッド clear も追加します。)
class backup : object ('a) val mutable copy : 'a option method clear : unit method restore : 'a method save : 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]`
クラス ['a] backup_ref : 'a -> object ('b) val mutable copy : 'b option val mutable x : 'a メソッド clear : unit メソッド get : 'a メソッド restore : 'b メソッド save : unit メソッド set : 'a -> unit 終了
`# 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;;`
15 再帰クラス
再帰クラスは、相互に再帰的な型を持つオブジェクトを定義するために使用できます。
クラス window : object val mutable top_widget : widget option method top_widget : widget option end および widget : window -> object val window : window method window : window end
それらの型は相互に再帰的ですが、クラス widget と window 自体は独立しています。
`# class virtual comparable = object (_ : 'a) method virtual leq : 'a -> bool end;;`
16 バイナリ メソッド
バイナリ メソッドは、self と同じ型の引数を取るメソッドです。以下のクラス Comparative は、型変数 'a が self の型にバインドされている、型 'a -> bool のバイナリ メソッド leq を持つクラスのテンプレートです。したがって、#comparable は < leq : 'a -> bool; .. > as 'a に展開されます。ここで、バインダー as によって再帰型も記述できることがわかります。
class virtual Comparative : 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;;`
次に、comparable のサブクラス money を定義します。クラス money は、float を比較可能なオブジェクトとしてラップするだけです。1 以下で、より多くの操作で money を拡張します。プリミティブ <= は OCaml の多態関数であるため、クラス パラメータ x に型制約を使用する必要があります。inherit 句により、このクラスのオブジェクトの型が #comparable のインスタンスであることが保証されます。
class money : float -> object ('a) val repr : float method leq : 'a -> bool method value : float end
`# class money2 x = object inherit money x method times k = {< repr = k *. repr >} end;;`
型 money は型 comparer のサブタイプではないことに注意してください。これは、メソッド leq の型で自己型が反変位置に出現するためです。実際、クラス money のオブジェクト m には、値メソッドにアクセスするため型 money の引数を期待するメソッド leq があります。型 comparer の m を考慮すると、メソッド値を持たない引数で m のメソッド leq を呼び出すことができ、これはエラーになります。
同様に、以下の型 money2 は型 money のサブタイプではありません。
class money2 : float -> object ('a) val repr : float method leq : 'a -> bool method times : float -> 'a method value : float end
`# let min (x : #comparable) y = if x#leq y then x else y;;`
ただし、money または money2 型のオブジェクトを操作する関数を定義することは可能です。関数 min は、#comparable で型が統一される 2 つのオブジェクトの最小値を返します。min の型は、#comparable -> #comparable -> #comparable と同じではありません。省略形 #comparable は型変数 (省略記号) を隠蔽するためです。この省略形が出現するたびに、新しい変数が生成されます。
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 型または money2 型のオブジェクトに適用できます。
バイナリ メソッドのその他の例は、セクション 8.2.1 および 8.2.3 にあります。
メソッド times の override の使用に注意してください。{< repr = k *. repr >} の代わりに new money2 (k *. repr) と記述すると、継承で適切に動作しません。money2 のサブクラス money3 では、times メソッドはクラス money2 のオブジェクトを返しますが、期待どおりクラス money3 のオブジェクトは返しません。
クラス 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;;`
クラス money: float -> object ('a) val repr: float メソッド leq: 'a -> bool メソッド plus: 'a -> 'a メソッド print: unit メソッド times: float -> 'a メソッド value: float end
17 Friends
上記のクラス money は、バイナリ メソッドでよく発生する問題を明らかにしています。同じクラスの他のオブジェクトとやり取りするには、value などのメソッドを使用して、money オブジェクトの表現を明らかにする必要があります。すべてのバイナリ メソッド (ここでは plus と leq) を削除すると、メソッド value も削除することで、オブジェクト内に表現を簡単に隠すことができます。ただし、バイナリ メソッドの一部が同じクラス (self 以外) のオブジェクトの表現にアクセスする必要がある場合、これは不可能になります。
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;;
フレンド関数の別の例は、セクション 8.2.3 にあります。これらの例は、オブジェクト (ここでは同じクラスのオブジェクト) と関数のグループが互いの内部表現を参照する必要がある一方で、それらの表現は外部から隠されている必要がある場合に発生します。解決策は常に、すべてのフレンドを同じモジュールで定義し、表現へのアクセスを許可し、シグネチャ制約を使用してモジュール外部で表現を抽象化することです。
1
浮動小数点は 10 進数の近似値であり、エラーが発生する可能性があるため、ほとんどの金銭計算には適していません。
(この章は Jérôme Vouillon、Didier Rémy、Jacques Garrigue が執筆)
Copyright © 2024 Institut National de Recherche en Informatique et en Automatique