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 の紹介

  • コア言語

  • モジュール システム

  • OCaml のオブジェクト

  • ラベル付き引数

  • 多態的バリアント

  • 多態性とその制限

  • 一般化された代数的データ型

  • クラスとモジュールを使用した高度な例

  • 並列プログラミング

  • メモリ モデル: 難しい部分

第 3 章 OCaml のオブジェクト

バージョン 5.2

< OCaml マニュアル

  • OCaml のオブジェクト

  • 1 クラスとオブジェクト

  • 2 即時オブジェクト

  • 3 自己への参照

  • 4 初期化子

  • 5 仮想メソッド

  • 6 プライベート メソッド

  • 7 クラス インターフェイス

  • 8 継承

  • 9 多重継承

  • 10 パラメータ化されたクラス

  • 11 ポリモーフィック メソッド

  • 12 強制の使用

  • [13 関数オブジェクト](objectexamples.html#s%3A functional-objects)

  • 14 オブジェクトのクローン作成

  • 15 再帰クラス

  • 16 バイナリ メソッド

  • 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


☰OCaml の紹介

第 4 章 ラベル付き引数

バージョン 5.2

< OCaml マニュアル

`- : f:('a -> 'b) -> 'a list -> 'b list = <fun>`

標準ライブラリの Labels で終わるモジュールを見ると、関数型に、自分で定義した関数にはなかった注釈があることがわかります。
`# StringLabels.sub;;`

`- : string -> pos:int -> len:int -> string = <fun>`

`# let f ~x ~y = x - y;;`

このような name: 形式の注釈は ラベル と呼ばれます。これらは、コードを文書化し、より多くのチェックを可能にし、関数の適用に柔軟性を与えることを目的としています。プログラム内の引数にこのような名前を付けるには、引数の前にチルダ ~ を付けます。

val f : x:int -> y:int -> int = <fun>
`# let x = 3 and y = 2 in f ~x ~y;;`

`- : int = 1`

`# let f ~x:x1 ~y:y1 = x1 - y1;;`

変数と型内に表示されるラベルに異なる名前を使用する場合は、~name: という形式の命名ラベルを使用できます。これは、引数が変数でない場合にも適用されます。
`# f ~x:3 ~y:2;;`

`- : int = 1`

val f : x:int -> y:int -> int = <fun>

ラベルは OCaml の他の識別子と同じルールに従います。つまり、予約キーワード (in や to など) をラベルとして使用することはできません。
`# let f ~x ~y = x - y;;`

`# f ~y:2 ~x:3;;`

形式パラメータと引数はそれぞれのラベルに従ってマッチングされ、ラベルがない場合は空のラベルとして解釈されます。これにより、アプリケーションで引数を交換できます。また、関数を任意の引数に部分的に適用して、残りのパラメータの新しい関数を作成することもできます。

val f : x:int -> y:int -> int = <fun>
`- : int = 1`

`# ListLabels.fold_left;;`

`- : f:('acc -> 'a -> 'acc) -> init:'acc -> 'a list -> 'acc = <fun>`

`# ListLabels.fold_left [1;2;3] ~init:0 ~f:( + );;`

`- : int = 6`

`# ListLabels.fold_left ~init:0;;`

`- : f:(int -> 'a -> int) -> 'a list -> int = <fun>`

`# let hline ~x:x1 ~x:x2 ~y = (x1, x2, y);;`

関数の複数の引数に同じラベルが付いている場合(またはラベルが付いていない)、それらの引数は互いに交換できず、順序が重要になります。ただし、他の引数とは交換可能です。

val hline : x:'a -> x:'b -> y:'c -> 'a * 'b * 'c = <fun>
`# hline ~x:3 ~y:2 ~x:5;;`

`- : int * int * int = (3, 5, 2)`

`# let bump ?(step = 1) x = x + step;;`

1 オプション引数

ラベル付き引数の興味深い特徴は、オプションにできることです。オプションのパラメータの場合、非オプションのパラメータのチルダ ~ が疑問符 ? に置き換えられ、関数型ではラベルの先頭に ? が付きます。このようなオプションのパラメータにはデフォルト値を指定できます。

val bump : ?step:int -> int -> int = <fun>
`# bump 2;;`

`- : int = 3`

`# bump ~step:3 2;;`

`- : int = 5`

オプション引数を取る関数は、少なくとも 1 つの非オプション引数も取る必要があります。オプション引数が省略されているかどうかを判断する基準は、関数型でこのオプション引数の後に現れる引数のラベルなし適用です。その引数にラベルが付いている場合は、関数を完全に適用し、すべてのオプション引数を省略し、残りのすべての引数のすべてのラベルを省略することによってのみ、オプション引数を削除できることに注意してください。
`# let test ?(x = 0) ?(y = 0) () ?(z = 0) () = (x, y, z);;`

`# test ();;`

`- : ?z:int -> unit -> int * int * int = <fun>`

val test : ?x:int -> ?y:int -> unit -> ?z:int -> unit -> int * int * int = <fun>
`# test ~x:2 () ~z:3 ();;`

`- : int * int * int = (2, 0, 3)`

`# test ~y:2 ~x:3 () ();;`

`- : int * int * int = (3, 2, 0)`

オプションのパラメータは、同時に適用される限り、オプションでないパラメータやラベルのないパラメータと交換することもできます。 本質的に、オプションの引数は、独立して適用されるラベルのない引数とは交換できません。
`# test () () ~z:1 ~y:2 ~x:3;;`

`- : int * int * int = (3, 2, 1)`

`# (test () ()) ~z:1 ;;`

エラー: この式の型は int * int * int です。これは関数ではないため、適用できません。

ここで (test () ()) はすでに (0,0,0) であり、これ以上適用できません。
`# let bump ?step x = match step with | None -> x * 2 | Some y -> x + y ;;`

オプション引数は、実際にはオプション型として実装されています。デフォルト値を指定しない場合は、その内部表現にアクセスでき、'a option = None | Some of 'a と入力します。引数が存在する場合と存在しない場合で異なる動作を提供できます。

val bump : ?step:int -> int -> int = <fun>
`# let test2 ?x ?y () = test ?x ?y () ();;`

`# test2 ?x:None;;`

関数呼び出しから別の関数呼び出しにオプション引数を中継することも便利です。これは、適用された引数の前に ? を付けることで実行できます。この疑問符は、オプション型でのオプション引数のラップを無効にします。

val test2 : ?x:int -> ?y:int -> unit -> int * int * int = <fun>
`- : ?y:int -> unit -> int * int * int = <fun>`

2 ラベルと型推論

ラベルとオプション引数は関数適用の記述の快適性を高めますが、言語の他の部分ほど完全に推論できないという落とし穴があります。

次の 2 つの例でそれを確認できます。
`# let h' g = g ~y:2 ~x:3;;`

`# h' f ;;`

val h' : (y:int -> x:int -> 'a) -> 'a = <fun>

エラー: この式の型は x:int -> y:int -> int ですが、式は y:int -> x:int -> 'a 型であるはずです
`# let bump_it bump x = bump ~step:2 x;;`

`# bump_it bump 1 ;;`

val bump_it : (step:int -> 'a -> 'b) -> 'a -> 'b = <fun>

エラー: この式の型は ?step:int -> int -> int ですが、式は step:int -> 'a -> 'b 型であるはずです
最初のケースは単純です。g には ~y が渡され、次に ~x が渡されますが、f は ~x と次に ~y を期待します。これは、g の型が x:int -> y:int -> int であることが事前にわかっている場合は正しく処理されますが、そうでない場合は上記の型の衝突が発生します。最も簡単な回避策は、形式パラメータを標準の順序で適用することです。

2 番目の例はより微妙です。引数 bump の型が ?step:int -> int -> int であることを意図していましたが、step:int -> int -> 'a と推論されます。これら 2 つの型は互換性がないため (内部的に通常の引数とオプションの引数は異なります)、bump_it を実際の bump に適用すると型エラーが発生します。

ここでは、型推論の仕組みを詳しく説明しません。上記のプログラムには、g または bump の正しい型を推測するのに十分な情報がないことを理解する必要があります。つまり、関数の適用方法だけを見ても、引数がオプションかどうか、または正しい順序がどれであるかを知る方法はありません。コンパイラが使用する戦略は、オプションの引数が存在せず、アプリケーションが正しい順序で実行されると想定することです。
`# let bump_it (bump : ?step:int -> int -> int) x = bump ~step:2 x;;`

`# bump_it bump 1;;`

オプション パラメータのこの問題を解決する正しい方法は、引数 bump に型注釈を追加することです。

val bump_it : (?step:int -> int -> int) -> int -> int = <fun>
`- : int = 3`

`# let twice f (x : int) = f(f x);;`

実際には、このような問題は、メソッドにオプション引数があるオブジェクトを使用するときに主に発生するため、オブジェクト引数の型を記述することは多くの場合良い考えです。

通常、関数に予期される型とは異なる型のパラメータを渡そうとすると、コンパイラは型エラーを生成します。ただし、予期される型がラベルのない関数型であり、引数がオプション パラメータを期待する関数である特定のケースでは、コンパイラは、すべてのオプション パラメータに None を渡すことによって、引数を予期される型と一致するように変換しようとします。
`# twice bump 2;;`

`- : int = 8`

val を 2 回実行します: (int -> int) -> int -> int = <fun>

この変換は、副作用を含め、意図されたセマンティクスと一致しています。つまり、オプション パラメータの適用によって副作用が発生する場合、これらの副作用は、受け取った関数が実際に引数に適用されるまで遅延されます。

3 ラベル付けの提案

名前と同様に、関数のラベルを選択するのは簡単な作業ではありません。良いラベル付けとは、次の条件を満たすラベル付けです。

  • プログラムが読みやすくなる、

  • 覚えやすい、

  • 可能な場合は、部分的な適用が便利になる。

​​ここでは、OCaml ライブラリのラベル付けに適用したルールについて説明します。

「オブジェクト指向」的に言えば、各関数には、メイン引数である object と、そのアクションに関連するその他の引数である parameters があると考えることができます。関数を関数と組み合わせることができるように、ラベル付けモードを切り替えられるようにするため、オブジェクトにはラベルを付けません。その役割は関数自体から明らかです。パラメータには、その性質または役割を思い起こさせる名前がラベル付けされます。最適なラベルは、性質と役割を組み合わせたものです。これが不可能な場合は、役割を優先します。性質は、多くの場合、型自体によって与えられるためです。わかりにくい略語は避けてください。

ListLabels.map : f:('a -> 'b) -> 'a list -> 'b list
UnixLabels.write : file_descr -> buf:bytes -> pos:int -> len:int -> unit

同じ性質と役割のオブジェクトが複数ある場合、それらはすべてラベル付けされません。

ListLabels.iter2 : f:('a -> 'b -> unit) -> 'a list -> 'b list -> unit

優先オブジェクトがない場合、すべての引数にラベルが付けられます。

BytesLabels.blit :
src:bytes -> src_pos:int -> dst:bytes -> dst_pos:int -> len:int -> unit

ただし、引数が 1 つしかない場合は、ラベル付けされないことがよくあります。

BytesLabels.create : int -> bytes

この原則は、各引数の役割があいまいでない限り、戻り値の型が型変数である複数の引数を持つ関数にも適用されます。このような関数にラベルを付ける場合、ListLabels.fold_left で見たように、アプリケーションでラベルを省略しようとしたときに厄介なエラー メッセージが表示されることがあります。

ライブラリ全体で見つかるラベル名の一部を以下に示します。

ラベル| 意味
---|---
f:| 適用される関数
pos:| 文字列、配列、またはバイト シーケンス内の位置
len:| 長さ
buf:| バッファとして使用されるバイト シーケンスまたは文字列
src:| 操作のソース
dst:| 操作の宛先
init:| イテレータの初期値
cmp:| 比較関数 (例: Stdlib.compare)
mode:|操作モードまたはフラグ リスト

これらはすべて提案に過ぎませんが、ラベルの選択は可読性にとって重要であることを覚えておいてください。奇妙な選択はプログラムの保守を困難にします。

理想的には、適切な関数名と適切なラベルがあれば、関数の意味を理解するのに十分です。この情報は OCamlBrowser または ocaml トップレベルで取得できるため、ドキュメントはより詳細な仕様が必要な場合にのみ使用されます。


« OCaml のオブジェクトポリモーフィック バリアント »

(Jacques Garrigue が執筆した章)

Copyright © 2024 Institut National de Recherche en Informatique et en Automatique

いいなと思ったら応援しよう!