最初に型とクラスを区別してから、サブタイプとサブクラスの違いを調べるのがおそらく便利です。
この回答の残りの部分では、議論中の型は静的な型であると仮定します(通常、サブタイプは静的なコンテキストで発生するため)。
ほとんどの言語は少なくとも部分的にそれらを融合するので、タイプとクラスの違いを説明するのに役立つおもちゃの擬似コードを開発します(簡単に触れておく正当な理由のため)。
タイプから始めましょう。タイプは、コード内の式のラベルです。このラベルの値と、それが他のすべてのラベルの値と一致するかどうか(一部の型システム固有の定義の場合)は、プログラムを実行せずに外部プログラム(型チェッカー)によって決定できます。それがこれらのラベルを特別なものにし、独自の名前にふさわしいものにしている。
おもちゃの言語では、そのようなラベルの作成を許可する場合があります。
declare type Int
declare type String
その後、さまざまな値にこのタイプのものとしてラベルを付けることができます。
0 is of type Int
1 is of type Int
-1 is of type Int
...
"" is of type String
"a" is of type String
"b" is of type String
...
これらのステートメントにより、タイプチェッカーは次のようなステートメントを拒否できるようになりました。
0 is of type String
型システムの要件の1つが、すべての式が一意の型を持つことである場合。
これがいかに不格好で、無限の数の式タイプを割り当てる際に問題が発生する可能性があるのかはさておきましょう。後で戻ることができます。
一方、クラスは、グループ化されたメソッドとフィールドのコレクションです(プライベートまたはパブリックなどのアクセス修飾子が含まれる可能性があります)。
class StringClass:
defMethod concatenate(otherString): ...
defField size: ...
このクラスのインスタンスは、これらのメソッドとフィールドの既存の定義を作成または使用する機能を取得します。
クラスのすべてのインスタンスがそのタイプで自動的にラベル付けされるように、クラスをタイプに関連付けることを選択できます。
associate StringClass with String
ただし、すべてのタイプにクラスを関連付ける必要があるわけではありません。
# Hmm... Doesn't look like there's a class for Int
おもちゃの言語では、すべてのクラスに型があるわけではなく、特にすべての式に型があるわけではない場合も考えられます。一部の式に型があり、一部の型にはない場合、型システムの一貫性ルールがどのようになるかを想像するのは少し難しいです(不可能ではありません)。
さらに、おもちゃの言語では、これらの関連付けは一意である必要はありません。2つのクラスを同じタイプに関連付けることができます。
associate MyCustomStringClass with String
ここで、式チェッカーが式の値を追跡する必要がないことを念頭に置いてください(ほとんどの場合、追跡することはできないか、不可能です)。知っているのは、あなたが言ったラベルだけです。以前の注意点として、0 is of type String
タイプチェッカーはステートメントを拒否することができました。これは、式に一意のタイプが必要であり、既に0
他の式にラベルを付けているという人為的に作成されたタイプルールのためです の値に関する特別な知識はありませんでした0
。
では、サブタイピングについてはどうでしょうか?よくサブタイプとは、タイプチェックの一般的なルールの名前で、他のルールを緩和します。つまりA is subtype of B
、タイプチェッカーがのラベルを要求するすべての場所でB
、A
。
たとえば、以前の番号ではなく、番号に対して次の操作を実行できます。
declare type NaturalNum
declare type Int
NaturalNum is subtype of Int
0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...
サブクラス化は、以前に宣言されたメソッドとフィールドを再利用できるようにする新しいクラスを宣言するための略記法です。
class ExtendedStringClass is subclass of StringClass:
# We get concatenate and size for free!
def addQuestionMark: ...
私たちは、仲間のインスタンスに持っていないExtendedStringClass
とString
我々が行ったようにStringClass
、それは全く新しいクラスのすべての後に、以来、私たちはずっととして記述する必要はありませんでした。これによりExtendedStringClass
、タイプString
チェッカーの観点からは互換性のないタイプを指定できます。
同様に、まったく新しいクラスを作成することもできましNewClass
た。
associate NewClass with String
のすべてのインスタンスは、タイプチェッカーの観点からStringClass
置き換えることができNewClass
ます。
そのため、理論上、サブタイピングとサブクラス化はまったく異なります。しかし、私が知っている言語には、実際にこのように型とクラスを持っているものはありません。私たちの言語を減らし始め、いくつかの決定の背後にある理論的根拠を説明しましょう。
まず、理論的にはまったく異なるクラスに同じ型を与えたり、クラスにクラスのインスタンスではない値と同じ型を与えたりすることもできますが、これはタイプチェッカーの有用性を著しく阻害します。タイプチェッカーは事実上、式内で呼び出しているメソッドまたはフィールドがその値に実際に存在するかどうかをチェックする機能を奪われます。これはおそらく、タイプチェッカー。結局のところ、そのString
ラベルの下に実際にある値が何であるかを誰が知っていますか。たとえば、concatenate
メソッドがまったくないものかもしれません。
それでは、すべてのクラスがそのクラスと同じ名前の新しい型を自動的に生成し、associate
その型を持つインスタンスをsに規定することにしましょう。私たちは取り除くことができますそれassociate
だけでなく、間に別の名前StringClass
とString
。
同じ理由で、おそらく一方が他方のサブクラスである2つのクラスのタイプ間のサブタイプ関係を自動的に確立する必要があります。すべてのサブクラスには、親クラスが持つすべてのメソッドとフィールドがあることが保証されていますが、その逆は当てはまりません。したがって、サブクラスは親クラスの型が必要なときはいつでも渡すことができますが、サブクラスの型が必要な場合は親クラスの型を拒否する必要があります。
これを、すべてのユーザー定義値がクラスのインスタンスでなければならないという規定と組み合わせると、is subclass of
二重の義務を引き、を取り除くことができますis subtype of
。
そしてこれにより、静的に型付けされた一般的なOO言語のほとんどが共有する特性に到達できます。「プリミティブ」型(例えばセットがありint
、float
どのクラスに関連付けられていないと、ユーザー定義されていないされているなど)。次に、自動的に同じ名前の型を持ち、サブクラス化でサブクラス化を識別するすべてのユーザー定義クラスがあります。
最後に、値とは別に型を宣言することの不格好さについて説明します。ほとんどの言語は2つの作成を統合するため、型宣言は、その型で自動的にラベル付けされるまったく新しい値を生成するための宣言でもあります。たとえば、クラス宣言は通常、型とその型の値をインスタンス化する方法の両方を作成します。これにより、いくつかの不格好さが解消されます。また、コンストラクターが存在する場合、1回のストロークで型を持つラベルを無限に作成できます。