2011-10-22

Dartがダウンキャストに警告を出さない理由

Dartでは、ダウンキャストには警告を発しない。

class S { }
class T extends S { }

void main()
{
    S s = new T() ; // アップキャスト
    T t = new S() ; // ダウンキャスト
}

この例では、静的型Tの変数tに対して、静的型Sで、実際の型もSのインスタンスを束縛させているが、これは警告を発しない。アップキャストに警告を発しないというのは自然だが、ダウンキャストに警告を発しないというのは、オブジェクト指向に馴染みのない人からすれば、不思議に思われるかも知れない。現にこの場合、実際の型はTではないのだから、この場合に限っていえば、エラーである。しかし、これは正しい挙動である。では、なぜ警告を発しないのか。

なぜならば、規格に明確に書かれている挙動だからである(13.4 Interface Types)。クラスは暗黙のインターフェースを持つので、この項目はクラスにも適用される。

規格で定義されているとしても、理由はなぜか。それは、Dartの型システムは、ほぼ確実に間違いであろうと思われるコードを見つけるためにあるからだ。間違いである可能性があっても、正当なコードである可能性もあるコードに対しては、警告を発しない。

ダウンキャストは、オブジェクト指向言語ならば、当然行うことである。

class S { }
class T extends S { }

void main()
{
    S s = new T() ; // アップキャスト
    T t = s ; // ダウンキャスト
}

これは、正しいコードである。変数sに束縛されているインスタンスの型は、まぎれもなくTである。したがって、このダウンキャストは正しい。もしこのコードに警告が発せられるとするならば、それは誤りである。このコードに警告を発するようでは、プログラマーは警告というものを信用しなくなってしまう。警告というのは、ほぼ確実に誤りである場合にのみ発せられるべきなのだ。

SとかTなどのやや抽象的な話ではわかりにくい人もいるかも知れない。もっと具体的な話をしよう。Dartではすべてのクラスは暗黙にObjectのサブクラスである。以下のコードは当然動くべきである。

void main()
{
    Object obj = "hello" ;
    String str = obj ; 
}

明らかに、このコードは正しいコードである。

7 comments:

Anonymous said...

さっそく記事にしていただき、ありがとうございます。
正直なところ、「ダウンキャストは、オブジェクト指向言語ならば、当然行うことである。 」とは少々意外でした。

JavaやC++で設計をする際、基本的には「ダウンキャストが発生しないように設計する」のが基本ですよね?
そもそもダウンキャストするくらいなら、最初からアップキャストすんなよって話です。

一番怖いのは、多人数で開発していた、誰かが「誤って、間違ったダウンキャストを書く」ことです。
もちろん、どうせ
String str = static_cast(obj);
なんて書けば、警告もエラーもなく間違ったダウンキャストをしのばすことはできますが、少なくともそんなことをしていないかどうか「検索」できるという利点がある気がします。

それともこんなことはIDEがやるべきことで、言語の仕様がサポートすべきことではない。といったところでしょうか?
IDEが「ダウンキャストを行っているところを列挙」してくれるのであれば、確かに「ダウンキャストに対する警告を抑制するための構文」は言語仕様的には冗長ですし、むしろ抑制された方が厄介な結果になる場合もあるかもしれません。

江添亮 said...

まあ、そういうツールもひとつの手でしょう。
Dartの型システムは、静的解析を助けるためにあるのです。
実行時のパフォーマンスだとかエラーチェックには、一切影響を及ぼしません。

Anonymous said...

Anonymousさんのご指摘はもっともでしょう。オブジェクト指向で大規模開発に携わり、苦労を重ねた経験がある人間であればダウンキャストは悪であると言うでしょう。JAVAもC#も、これを避けるための進化を遂げてきたのは御存知のことと思います。

また、多人数で開発を行う場合には、静的チェックはなるべくおせっかいのほうが役に立つのです。構文的には正しいが、プログラマが本当にそれを意図したのか疑わしい場合には警告を発してくれる方が助かります。コンパイラの警告には飽き足らず、lint的なツールを別個に導入しているプロジェクトにも携わりましたが、これは潜在的バグの発見に役立ちました。

それを「ダウンキャストは、オブジェクト指向言語ならば、当然行うこと」と言ってみたり、「警告というのは、ほぼ確実に誤りである場合にのみ発せられるべきなのだ」などと言うのは現実を知らないんでしょうね。

言語マニアの立場で講釈をたれるのは結構ですが、(実際、あなたの重箱の隅をつつく様な内容にもときどき興味深いものがあります。)したり顔で世間知らずな考えをもっともらしく披露して素直な方を混乱させるのはどうかとおもいます。

江添亮 said...

そもそも、オブジェクト指向は何も言語による支援が必要な概念ではないのです。
たとえば、あるCの構造体に、構造体の種類を示すメンバー変数があったとしたら、

enum Shape_Type { triangle, quadrilateral, circle, } ;

struct Shape
{
Shape_Type type ;
} ;

この場合、Shapeは親クラスであり、Shape::typeの値はいわば、クラスの実際の型、すなわち子クラスを示す実行時型情報といういえます。
言語による支援はないとしても、根本的には同じです。

この情報に依存して、与えられたShape型のオブジェクトのtypeを調べ、条件分岐して、それぞれ別の処理を行うようなコードは、根本的には、親クラスという抽象的な型から実行時の具体的な型を取得しているので、ダウンキャストをしていると言えます。

江添亮 said...

もちろん、このように直接プログラマーにコードを書かせるのは誤りのもとであるので、C++では、構造体ではなくクラスという概念を導入し、クラスはvirtual関数を持てるようにし、プログラマーを実行時の型チェックから、コードの上では解放したのです。

そもそも、Dartの思想は、
「開発時に厳格な型チェックが行われるのは面倒であるから型はオプショナルとする。プログラマーは実際のコードではなく、型情報を来にしなければならない。これはプログラマーを実際の問題の解決から遠ざける」
というものなので、まず確実に問題になる場合のみに警告を発するのは当然です。

Anonymous said...

せっかく返事をいただいたのにまた批判するのは忍びないのですが、「そもそも、オブジェクト指向は何も言語による支援が必要な概念ではないのです。」などといったことをわかったようにまたポンと書くから、あなたは現実を知らないというのです。そんなことは誰でも分かっていることです。C++も昔はCへのトランスレータでしたしね。ですが、私はそんな形而上学的な話をしているのではなく、現実の世界のことを言っているのです。

先の私のコメントで、C#やJAVAの進化と書いたのは、具体的にはジェネリックのことです。机上の空論ではなく、実際に業務でコーディングする際には、既成のライブラリのコンテナを使うことが多いですが、ジェネリック対応以前は、コンテナ内のアイテムを使用する際にはダウンキャストを使わざるを得ませんでした。

私はDartについては知らないので、あなたがDartの構文に限定して講釈をたれるのであれば、「ああ、Dartではそうですか。」と素直に読むこともできそうなものですが、それを調子に乗って一般化し、「OOPとはこうである」とか、「プログラマーとは云々」などという知ったかぶりをするから、分かったようなことを言うのはおよしなさい、と言いたくなった次第です。

Anonymous said...

まあ、現実的には駄プログラマが多いので、がっちがちな縛りが必要ってことで。