Dartにおけるジェネリックの意義
Google+で面白い議論がなされていたので簡単にまとめてみる。
元の投稿はDartのジェネリックがC#と違うことに疑問を投げている
投稿者は具体的には以下の様なコードを実現したいらしい
abstract class MyMessage {} class AMessage extends MyMessage{} class BMessage extends MyMessage{} abstract class IHandler<T>{ void handle<T>(T item); } class Handler implements IHandler<MyMessage> { void handle<AMessage>(AMessage msg){} void handle<BMessage>(BMessage msg){} }
そもそもDartは動的型付け言語である
投稿者はC#と比較してDartのジェネリックの不便さを嘆いているが、忘れてはいけないのは「Dartは動的型付け言語である」ということである。Dartで使われているのは厳密には型ではなくType Annotationという注釈でしかなく、基本的には静的解析のパフォーマンスを上げるために用意されている、エディターのための機能である。
その点で、3のリフレクションを用いてジェネリックタイプのインスタンス生成ができないというのは当然である。なぜなら実行時にはユーザーが定義した型など存在していないからである。そのためにDartの基底クラスObjectにはnoSuchMethod
が定義され、オーバーライドが可能になっている。
1と2についてはそもそもDartにメソッドのオーバーロードができない以上不可能である。Dartはすべてのジェネリックについてdynamic型を受け付けるので衝突が避けられないのだ。C#やJava等では、List<T>
とList
は別の型であるが、Dartでは同じである。List
は暗黙的にList<dynamic>
となるし、class List<T>
はnew List()
を許容する。
解決策
これらを踏まえた上で、最初に挙げたコードを実現しようとすると次のようなコードが考えられる。既定クラスで引数を受けておいて、実装側で引数の型によって処理を分けることができる。
abstract class MyMessage {} class A extends MyMessage{} class B extends MyMessage{} abstract class IHandler<T>{ void handle(T message); } class MyHandler implements IHandler<MyMessage> { handle(MyMessage msg){ if(msg is A){ ... }else if(msg is B){ ... } } }
もしくはIHandler
側で型ごとに予めメソッドを定義しておくのも選択肢である
abstract class MyMessage {} class A extends MyMessage{} class B extends MyMessage{} abstract class IHandler{ void handleA(AMessage message); void handleB(BMessage message); } class MyHandler implements IHandler { handleA(AMessage msg){} handleB(BMessage msg){} }
所感
自分ももともとC#書いてた身なので投稿者の気持ちはよく分かるが、Dartが実行時には動的型付けであるということを意識してコードを書くようになってからは、むしろこのような「オーバーロード禁止」や「メソッドのジェネリック禁止」は合理的だと思えている。型はランタイムのためではなくコードを書くときだけに利用されるDartの仕組みは、「厳密に書き、柔軟に動く」という理想の言語を実現することができるのではなかろうか。
[追記] Twitterで「メソッドのジェネリックサポートはAcceptedされてる」と指摘を受けたので調べた。
確かに受理されているが、1年以上放置されているので後回しにされているようだ。あまり期待はできないが一応可能性はあるということを覚えておく。