クラスはモジュール定義と似たようなミニ言語を使って定義されます。
クラス型はクラスにおける型式で、クラスの概形と型特性を規定します。
class-type | ::= | class-body-type |
| | [ [ ? ] label-name : ] typexpr -> class-type | |
class-body-type | ::= | object [ ( typexpr ) ] { class-field-spec } end |
| | class-path | |
| | [ typexpr { , typexpr } ] class-path | |
class-field-spec | ::= | inherit class-type |
| | val [ mutable ] [ virtual ] inst-var-name : typexpr | |
| | method [ private ] method-name : poly-typexpr | |
| | method [ private ] virtual method-name : poly-typexpr | |
| | constraint typexpr = typexpr |
class-path という式はその名前に束縛されたクラス型と等価です。
同様に、 [ typexpr1, ... , typexprn
] はその名前に束縛された、パラメータ化されたクラスと等価で、型パラメータは各々 typexpr1, ..., typexprn
にインスタン化されます。
typexpr -> class-type はクラス関数(値からクラスへの関数)の型で、 typexpr 型の値を引数に取り、結果として class-type 型のクラスを返すものを表します。
object [( typexpr )] { class-field-spec } end はクラス本体の型で、インスタンス変数とメソッドを規定します。 このとき、 typexpr は自身の型とマッチされます。 すなわち、自身の型への束縛になります。
クラス本体がクラス型に規定されたすべての構成要素を定義し、それらの型がクラス型で与えられた型要求を満たす場合、クラス本体はクラス本体の型にマッチします。 さらに、クラス本体に存在するメソッドは virtual であれ public であれ、クラス型にも存在していなければなりません(一方で、インスタンス変数や具象 private メソッドは省略してもかまいません)。 抽象メソッドは具象メソッドにマッチします。 抽象メソッドを使うと実装を捨象できます。 変更可能なインスタンス変数は変更可能なインスタンス変数にマッチします。
継承構文 inherit class-type を使うと他のクラス型からメソッドとインスタンス変数を取り込むことができます。 取り込まれたインスタンス変数とメソッドの型は現在のクラスの型うに追加されます。
インスタンス変数の仕様は val [mutable] [virtual] inst-var-name : typexpr と書きます。 ここで、 inst-var-name はインスタンス変数の名前で、 typexpr はそれに期待される型です。 mutable はそのインスタンス変数が物理的に変更できるかどうかを表します。 virtual はこのインスタンス変数が初期化されていないことを示します。 未初期化のインスタンス変数は継承を用いてあとで初期化することができます。
インスタンス変数の仕様を書くと、同名のインスタンス変数の仕様はすべて隠蔽されます。
メソッドの仕様は [private] method-name : poly-typexpr と書きます。 ここで、 method-name はメソッドの名前であり、 poly-typexpr はメソッドに期待される型で、多相型にすることができます。 private は、そのメソッドがオブジェクトの外側からはアクセスできないことを表します。
public なメソッドの仕様では暗黙にメソッドの多相性が保たれることがあります。 クラスのパラメータで束縛されない型変数で、クラス仕様内の他の部分に現れないものは、全称性のものと仮定され、結果のメソッド型で多相型になります。 多相型を明示するとこのような振舞いにはなりません。
同一のメソッドに対する複数の仕様は整合性のある型でなければなりません。 あるメソッドに private でない仕様があると、そのメソッドは public になります。
抽象メソッドの仕様は [private] virtual method-name : poly-typexpr と書きます。 ここで、 method-name はメソッドの名前で、 poly-typexpr はそれに期待される型になります。
クラス式はクラスにおける値式で、クラスに評価され、クラス型の表す仕様に対する実装を表すものになります。
class-expr | ::= | class-path |
| | [ typexpr { , typexpr } ] class-path | |
| | ( class-expr ) | |
| | ( class-expr : class-type ) | |
| | class-expr { argument }+ | |
| | fun { parameter }+ -> class-expr | |
| | let [ rec ] let-binding { and let-binding } in class-expr | |
| | object [ ( pattern [ : typexpr ] ) ] { class-field } end | |
class-field | ::= | inherit class-expr [ as value-name ] |
| | val [ mutable ] inst-var-name [ : typexpr ] = expr | |
| | val [ mutable ] virtual inst-var-name : typexpr | |
| | method [ private ] method-name { parameter } [ : typexpr ] = expr | |
| | method [ private ] method-name : poly-typexpr = expr | |
| | method [ private ] virtual method-name : poly-typexpr | |
| | constraint typexpr = typexpr | |
| | initializer expr |
class-path はその名前に束縛されたクラスに評価されます。
同様に、 [typexpr1, ..., typexprn
] class-path はその名前に束縛された、パラメータ化されたクラスに評価され、型パラメータがそれぞれ typexpr1, ..., typexprn
にインスタンス化されます。
(class-expr) は class-expr と同一のクラス[5]に評価されます。
( class-expr : class-type ) は class-type が class-expr の型にマッチするか(すなわち、実装である class-expr が、型仕様である class-type に適合するか)検査します。 式全体は class-expr と同じクラスに評価されますが、 class-type に指定されていない要素はすべて隠蔽され、アクセスできなくなります。
クラスの適用は(ラベルのついた)式を並べることであらわされます。 これはもとの構築子を引数列に適用する構築子を持つクラスを表します。 引数は式の適用のときと同じように評価されますが、構築子自体はオブジェクトを生成するときにだけ評価されます。 特に、構築子の適用によって引き起こされる副作用はオブジェクトの生成時にだけ起こります。
fun [[?]label-name:] pattern -> class-expr は値からクラスへの関数に評価されます。
この関数が値 v
に適用されると、その値が pattern とマッチされ、パターンマッチにより拡張された環境で class-expr を評価した結果がこの式の値になります。
デフォルト値のある関数からパターンのみの関数への変換は通常の関数と同様に行われます。
fun parameter1 ... parametern
-> class-expr
は
fun parameter1 -> ... fun parametern
-> class-expr
の省略形です。[6]
let および let rec 構文は、通常の式における場合と同じく、名前を局所的に束縛します。
クラス定義の最初に局所定義が現れた場合には、その定義はクラスが生成されるときに(単純に、その定義がクラス定義の外側にあるように)評価されます。 それ以外の場合には構築子が呼び出されたときに評価されます。
class-body | ::= | [ ( pattern [ : typexpr ] ) ] |
| | { class-field } |
object class-body end はクラスの本体を表します。 これはクラスのプロトタイプで、クラスに属するオブジェクトの持つインスタンス変数やメソッドを並べます。
クラス本体はクラス値で、評価されるのは一回だけではなく、オブジェクトが作られるたびにその構成要素が評価されます。
クラス本体では、 ( pattern [: typexpr] ) というパターンは自身とマッチされます。 したがって、これにより自身と自身の型が束縛されます。 これらの束縛はメソッドと初期化子の中でだけ使うことができます。
自身の型を閉じたオブジェクト型にすることはできないので、クラスは拡張可能なままになります。
継承構文 inherit class-expr を使うと他のクラスのメソッドやインスタンス変数を再利用できます。 クラス式 class-expr はクラス式に評価され、このクラスのインスタンス変数、メソッド、初期化子が現在のクラスに追加されます。 メソッドを追加すると以前に定義された同名のメソッドはすべて上書きされます。
上記のクラス構文の後に as value-name を続けることで親クラスを value-name に束縛することができます。 ここで、 value-name は真の変数ではなく、メソッドを選択するためにだけ使うことができます。 例えば、 value-name # method-name とすることで、 method-name が現在のクラスで再定義されていた場合でも、親クラスで定義されたものをそのまま参照することができます。 この親クラスのスコープは現在のクラスに限定されています。 親クラスのメソッドを子クラスから呼ぶことはできますが、間接的にしか呼ぶことはできません。
val [mutable] inst-var-name = expr と定義すると、初期値が式 expr の値であるインスタンス変数 inst-var-name が追加されます。 mutable をつけるとこの変数をメソッドから物理的に変更できるようになります。
インスタンス変数はクラス内で後続するメソッドや初期化子内だけで使うことができます。
version 3.10 から、可視なインスタンス変数を同名のもので再定義した場合、変数を新たに作らず、最後の初期化式の値を使って変数を結合することになりました。 このとき、それぞれの定義の型と変更可能性は同じでなければなりません。 ただし、インタフェースから除外することでそのインスタンス変数を隠蔽していた場合には、そのインスタンス変数は同名のその他の変数とは区別されます。
抽象[7]インスタンス[8]変数の定義は val [mutable] virtual inst-var-name : typexpr と書きます。 値が変更可能であるか指定でき、型を指定します。
抽象インスタンス変数は version 3.10 で追加されました。
メソッド定義は method method-name = expr と書きます。 この定義により先行する同名のメソッドの定義はすべて上書きされます。 定義のうちのいずれかが public である(private でない)と指定されていれば、メソッドは public になります。
private メソッド method private method-name = expr は自身から(同一のオブジェクトの別のメソッドで、現在のクラスか、子クラスで定義されたものから)起動することができます。 value-name # method-name のようにして起動します。 value-name はクラス定義の先頭で自身を指すように束縛されたものです。 private メソッドはオブジェクトの型の中には登場しません。 ひとつのメソッドに public な定義と private な定義の両方を与えることができますが、 public なものがひとつでもあれば、残りの定義はすべて public になります。
メソッドが多相型を持つことを明示することもでき、これによりそのメソッドをプログラム中で(同一のオブジェクトに対しても)多相的に扱うことができるようになります。 多相性を明示するには次のいずれかの方法を使います。 (1) メソッド定義中で、メソッド名の直後に多相型を明示する。 method [private] method-name : {' ident}+. typexpr = expr (2) 抽象メソッド定義を使ってまえもって多相型を明示して宣言する。 (3) 継承や型制約を使ってそのような宣言を取り込む。
メソッド本体ではインスタンス変数を操作したり、自身を複製したりするための特別な式を使うことができます。
expr | ::= | ... |
| | inst-var-name <- expr | |
| | {< [ inst-var-name = expr { ; inst-var-name = expr } ] >} |
inst-var-name <- expr は inst-var-name に対応する値を expr の値で置き換え、現在のオブジェクトをその場で変更します。 変更対象のインスタンス変数は mutable と宣言されていなければなりません。
{< [inst-var-name = expr { ; inst-var-name = expr } ] >} は現在のオブジェクトのインスタンス変数 inst-var-name1 ... inst-var-namen
を 対応する式 expr1 ... exprn
の値で置き換えた、コピーを返します。
抽象[9]メソッドの定義は method [private] virtual method-name : poly-typexpr と書きます。 メソッドが public か private かと、その型を指定します。 メソッドの型を多相的にする場合は、それを明示しなければなりません。
constraint typexpr1 = typexpr2 はふたつの型が等しいことを強制します。 典型的には型パラメータを指定するのに使います。 これを使うと型パラメータを指定された型式に束縛することができます。
クラス初期化子 initializer expr はクラスからオブジェクトが生成され、インスタンス変数がすべて初期化された時点で評価されます。
class-definition | ::= | class class-binding { and class-binding } |
class-binding | ::= | [ virtual ] [ [ type-parameters ] ] class-name { parameter } [ : class-type ] = class-expr |
type-parameters | ::= | ' ident { , ' ident } |
クラス宣言は class class-binding { and class-binding } は再帰的です。 各 class-binding は継承以外のすべての式で参照できる class-name を定義します。 定義された名前は、後続する定義では継承にも使うことができます。
class-binding はクラス名 class-name を式 class-expr の値に束縛します。 また、クラス型 class-name をそのクラスの型に束縛し、 class-name と #class-name のふたつの略記型を定義します。 前者はこのクラスのオブジェクトの型で、後者はより一般的な、任意の子クラスのオブジェクトの型も単一化したものです( 6.4 節「型式 」 参照)。
クラスに抽象メソッド(すなわち、クラス型に現れながら、実際には定義されていないメソッド)がある場合、定義に virtual をつけなければなりません。 抽象クラスからオブジェクトを作ることはできません。
class-specification | ::= | class class-spec { and class-spec } |
class-spec | ::= | [ virtual ] [ [ type-parameters ] ] class-name : class-type |
クラス仕様はクラス定義においてシグネチャに相当するものです。 クラス仕様に同一の型パラメータがあり、型がマッチする場合、そのクラス仕様はクラス定義にマッチします。
classtype-definition | ::= | class type classtype-def { and classtype-def } |
classtype-def | ::= | [ virtual ] [ [ type-parameters ] ] class-name = class-body-type |
クラス型定義 class class-name = class-body-type は、クラス本体の型 class-body-type の略記型 class-name を定義します。 クラス定義と同じく、ふたつの略記型 class-name と #class-name も定義されます。 定義に型パラメータをつけてパラメータ化することもできます。 クラス型本体に virtual とされたメソッドがある場合、クラス型定義に virtual をつけなければなりません。
ふたつクラス型定義に、同一の型パラメータがあり、展開された各型がマッチするとき、ふたつの定義はマッチします。