TypeScriptを使うとある程度対応できることがわかりました。
ダウンキャストをなくすためには下記の2つの機能が言語に必要となります。
- 継承可能な型引数をサポートすること
- ジェネリックワイルドカードをサポートすること
このうち、継承可能な型引数については TypeScript
で型引数の既定値を指定できるので、イメージしていたものとは異なりますが、これを利用すれば部分的に対応できることがわかりました。
中身は BaseContent⇒DerivedContent⇒DerivedContent2 と継承します。一方、入れ物はこれに対応して、BaseBox⇒DerivedBox⇒DerivedBox2 と継承します。
BaseBoxは中身を参照するcontentプロパティを持ち、これをTContent型としています。入れ物の継承に従ってこの型引数も制約の型をより継承した中身の型へと変えていきます。このようにすることで、contentへの代入は型安全が保たれます。また、サンプルプログラムでは省略していますが、各入れ物クラスの中でcontentのハンドリングの際にダウンキャストは発生しません。
1 | class BaseContent { |
次に対応できない部分について説明します。
1 | const baseBox: BaseBox = derivedBox2; //本来なら型引数が異なるので代入互換性はないはずだが代入できる。 |
上記のコードで、DerivedContent2からBaseBoxへ継承関係にあるので、代入できると考えがちです。実際、TypeScript はこれを許可しています。しかし、継承に従って型引数が変わっているので、本来は代入互換性がないはずなのです。サンプルではダウンキャストを全く使っていないにもかかわらず、型安全でない状況が生まれます。
これを型安全にするためには前の記事でもふれたようにジェネリックワイルドカードの機能が必要です。コメント内にもしワイルドカードが使えたらどのようなコードになるかを書いています。
問題はもう一つ残っていて、入れ物の継承で中身の型である TContent が継承するたびに別物になっていることです。これも同じ型引数のまま継承させる必要があります。
言語を開発するほどの技術力と財力がありませんので、このような言語が開発されたらいいなと、願っているだけです。