最初に純粋なパラメトリック多相性について説明し、後で有界多相性について説明します。
パラメトリック多型とはどういう意味ですか?まあ、それは型、またはむしろ型コンストラクタが型によってパラメータ化されることを意味します。型はパラメータとして渡されるため、事前にそれが何であるかを知ることはできません。それに基づいて仮定することはできません。さて、あなたはそれが何であるかわからない場合、それから何が使用されますか?それで何ができますか?
たとえば、それを保存および取得できます。あなたが既に言及したのは、コレクションです。リストまたは配列にアイテムを保存するために、アイテムについて何も知る必要がありません。リストまたは配列は、タイプを完全に無視できます。
しかし、Maybe
タイプはどうですか?あなたがそれに精通してMaybe
いないなら、多分値を持っているかもしれないし、そうでないかもしれない型です。どこで使用しますか?たとえば、ディクショナリからアイテムを取得する場合:アイテムがディクショナリにない可能性があるという事実は例外的な状況ではないため、アイテムが存在しない場合は例外をスローしないでください。代わりに、のサブタイプのインスタンスを返します。このインスタンスにはMaybe<T>
、との2つのサブタイプがNone
ありSome<T>
ます。int.Parse
はMaybe<int>
、例外またはint.TryParse(out bla)
ダンス全体をスローする代わりに、実際に返す必要がある何かの別の候補です。
さて、あなたはそれMaybe
がゼロまたは1つの要素しか持つことができないリストのようなちょっとしたものであると主張するかもしれません。したがって、ちょっとしたコレクションです。
では、どうでしょうTask<T>
?これは、将来のある時点で値を返すことを約束するタイプですが、必ずしも現在値を持っているとは限りません。
それともどうFunc<T, …>
?型を抽象化できない場合、ある型から別の型への関数の概念をどのように表現しますか?
または、より一般的には、抽象化と再利用がソフトウェアエンジニアリングの2つの基本的な操作であると考えると、なぜ型を抽象化できるようにしたくないのでしょうか。
それでは、境界のあるポリモーフィズムについて話しましょう。有界ポリモーフィズムは、基本的にパラメトリックポリモーフィズムとサブタイプポリモーフィズムが出会う場所です。タイプコンストラクターのタイプパラメーターを完全に無視する代わりに、タイプを特定のタイプのサブタイプにバインド(または制約)できます。
コレクションに戻りましょう。ハッシュテーブルを取ります。上記で、リストはその要素について何も知る必要がないと述べました。ハッシュテーブルはそうです:ハッシュテーブルをハッシュできることを知る必要があります。(注:C#では、すべてのオブジェクトが等しいかどうかを比較できるように、すべてのオブジェクトはハッシュ可能です。ただし、これはすべての言語に当てはまるわけではなく、C#でも設計ミスと見なされることがあります。
そのため、ハッシュテーブルのキータイプのタイプパラメータを次のインスタンスに制限しますIHashable
。
class HashTable<K, V> where K : IHashable
{
Maybe<V> Get(K key);
bool Add(K key, V value);
}
代わりにこれがあったと想像してください:
class HashTable
{
object Get(IHashable key);
bool Add(IHashable key, object value);
}
あなたはvalue
そこから出て何をしますか?あなたはそれで何もすることができません、あなたはそれがオブジェクトであることを知っているだけです。そしてそれを反復すると、あなたが知っているものIHashable
(それは1つのプロパティしか持っていないのであなたにはあまり役に立たないHash
)とあなたが知っている何かobject
(あなたはさらに少ないのに役立ちます)のペアです。
またはあなたの例に基づいたもの:
class Repository<T> where T : ISerializable
{
T Get(int id);
void Save(T obj);
void Delete(T obj);
}
アイテムはディスクに保存されるため、シリアル化可能である必要があります。しかし、代わりにこれがある場合はどうなりますか:
class Repository
{
ISerializable Get(int id);
void Save(ISerializable obj);
void Delete(ISerializable obj);
}
一般的なケースでは、あなたが入れた場合BankAccount
では、あなたが得るBankAccount
背中を、のようなメソッドとプロパティを持つOwner
、AccountNumber
、Balance
、Deposit
、Withdraw
、などあなたが働くことができる何か。さて、他のケースは?aを入力しBankAccount
ますがSerializable
、プロパティが1つしかないを取得しますAsString
。あなたはそれで何をするつもりですか?
また、有界ポリモーフィズムを使用して実行できるいくつかの巧妙なトリックもあります。
F境界の定量化は、基本的に型変数が制約に再び現れる場所です。これは状況によっては便利です。たとえば、ICloneable
インターフェイスをどのように記述しますか?戻り値の型が実装クラスの型であるメソッドをどのように記述しますか?MyType機能を備えた言語では、簡単です。
interface ICloneable
{
public this Clone(); // syntax I invented for a MyType feature
}
有界ポリモーフィズムのある言語では、代わりに次のようなことができます:
interface ICloneable<T> where T : ICloneable<T>
{
public T Clone();
}
class Foo : ICloneable<Foo>
{
public Foo Clone()
{
// …
}
}
これはMyTypeバージョンほど安全ではないことに注意してください。なぜなら、誰かが単に「間違った」クラスを型コンストラクタに渡すことを妨げるものは何もないからです。
class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
public SomethingTotallyUnrelatedToBar Clone()
{
// …
}
}
抽象型メンバー
結局のところ、抽象型のメンバーとサブタイピングがあれば、実際にはパラメトリックなポリモーフィズムがまったくなくても同じことができます。Scalaはこの方向に向かっています。これは、ジェネリックで始まり、それらを削除しようとする最初の主要な言語です。これは、JavaやC#などとまったく逆です。
基本的に、Scalaでは、フィールド、プロパティ、メソッドをメンバーとして持つことができるように、型も持つことができます。また、フィールドやプロパティ、メソッドを抽象のままにして、後でサブクラスに実装できるように、型メンバーも抽象のままにすることができます。List
C#でサポートされていれば、次のようなシンプルなコレクションに戻りましょう。
class List
{
T; // syntax I invented for an abstract type member
T Get(int index) { /* … */ }
void Add(T obj) { /* … */ }
}
class IntList : List
{
T = int;
}
// this is equivalent to saying `List<int>` with generics
interface IFoo<T> where T : IFoo<T>
も思い出しました。それは明らかに実際のアプリケーションです。例は素晴らしいです。しかし、何らかの理由で私は満足していません。私はむしろ、それが適切であるときとそうでないときを考えてみたい。ここでの回答はこのプロセスにある程度貢献していますが、私はまだこのすべてについて不愉快だと感じています。言語レベルの問題はもう長い間気にしないので、それは奇妙です。