«前の日記(2007-06-11 (月)) 最新 次の日記(2007-06-13 (水))»  

まちゅダイアリー


2007-06-12 (火)

Duck Typing

rubyco(るびこ)の日記 - Duck Typingは大規模プロジェクトでも大丈夫か?に反応。コメント欄に書いてたら長くなっちゃったのでこっちに。

そういえば、Duck Typingでは「メソッド名がグローバル」になりますね…。ふと思ったのですが「大規模プロジェクトでメソッド名がコンフリクトしてDuck Typingが破綻する」という可能性はあるでしょうか?

(0) この問いかけ自体が無意味。なぜなら…(誰かがここを埋める)

(1) 大規模プロジェクトでもDuck Typingは破綻しない。なぜなら…(誰かがここを埋める)

(2) 大規模プロジェクトでDuck Typingは破綻する可能性はある。でも、その可能性はほかの型機構でも似たり寄ったり。

(3) その他。

んー。(2)かなぁ。  個人的には大規模かどうかはあんまり関係ない気がしてきた。

Duck Typing を使う基準として、以下の「どちらか」が満たされていると考えてみる。

  • 処理対象となる変数の型がほぼ予測可能であること。例えば、元の例のように整数か文字列のどちらかといった具合。
  • 処理対象のオブジェクトが呼び出すメソッド名が広く使われているものであること。 to_i や to_arr のように。

変数の型が予測可能であれば、別のセマンティクスを持つオブジェクトが紛れ込む可能性は低い。 また、広く使われている to_i や to_arr しか使わないのであれば、これまた別のセマンティクスを持つオブジェクトが紛れ込む可能性は低い。

問題が起きるとすれば、以下のような場合になる。

  1. 処理対象となる変数が様々な型を持つ可能性があり、そこに想定していない型が紛れ込んでしまった。
  2. ある型において、 to_i や to_arr を別の意味で再定義してしまった場合。
list = [100, "10", "AAA"]
list.each do |item|
  puts item.to_i + 1
end

でもこれは、 Duck Typing を使わなかったとしても発生する問題。

変数に何の型が入るか分からないのであれば、元の例の Integer(s) + 1 のように変数の型をキャストすることになる。 この場合でも、想定外の型が入ってしまうと、キャストに失敗してエラーとなるか、想定外の変換によって意図しない動作をするかになる。

厳密にチェックしようとすると、静的型付け言語を使うことになるんだろうけど、様々な型を扱おうとするとコンテナは Object 型になる*1。 そうするとコンテナに入る型は実行時にしか決まらないから、想定していない型が紛れ込んでしまった時の問題は動的型付けの場合と変わらなくなっちゃう。

ArrayList list = new ArrayList();
list.add(100);
list.add("10");
list.add("AAA");

Iterator item = list.iterator();
while (item.hasNext()) {
  System.out.println((Integer)item.next() + 1);
}

さらに厳密にやるなら、インタフェースを一つ定義して、処理対象となる型全てに対してインタフェースを実装してもらうようにすることになる。 でもそれで得られるメリットは、実行時に検出していた問題をコンパイル時に検出できるかどうかに過ぎない。 インタフェースを実装する時間と比較して、コストパフォーマンスがあるかどうかは分からない。 それに、インタフェースをベースにする場合でも、インクルードするインタフェースが増えるとメソッド名は衝突しちゃう。

結局、 Duck Typing を使っても使わなくても、問題は同じだと思う。 要は、多態性と複雑さのバランスをどう取るかで、バランス取りに失敗したら Duck Typing とインタフェースの実装のどちらも収集が付かなくなっちゃう。 ちょっと前にどこかで話題になっていた、「自由度が高すぎれば良い訳じゃない。選択肢を絞ることも大切」という話に似ているかも。

参考

4274066428

ちなみに、プログラミングRuby言語編(第2版)の第23章が、まるごとDuck Typingの話題になってる。

静的型付けによってプログラムの信頼性が向上するという証拠はない

Person オブジェクトを入れたら、 Person オブジェクトを取り出します。そうでないプログラムを書く人などいません。

結局、 duck typing というのはルールでもなんでもなく、あくまでプログラミングスタイルの1スタイルにすぎません。偏執狂的なチェックと柔軟なチェックのバランスをうまくとってプログラムを設計するようにしてください。

ということなので、適材適所で使っている限りは(2)なんだと思う。

Tags: Ruby

*1 Java1.2の頃の知識なので、1.5以降は違うかも…

本日のツッコミ(全5件) [ツッコミを入れる]
みずしま (2007-06-12 (火) 17:14)

はじめまして。注釈で書かれておられる通り、Java 1.5ではGenericsが存在するため、事情が違います。<br>具体的には、toIntegerを持つインタフェースIntegerCompatibleを定義して、<br><br>List<IntegerCompatible> list = new ArrayList<IntegerCompatible>();<br>...<br>for(IntegerCompatible i : list) {<br> System.out.println(i.toInteger() + 1);<br>}<br><br>のようにする事になると思います。ただ、組み込みクラスにおいて、IntegerCompatibleに相当するクラスは存在しないため、既存のクラス(Stringとか)をIntegerCompatibleに適合させるためには、ラッパークラスを作る必要がありそうです。

まちゅ (2007-06-12 (火) 17:23)

ツッコミ感謝です。<br>Genericsを使うとコンパイル時に型異常をチェックできるという理解でよいでしょうか。<br>(IntegerCompatibleに対応していない型をaddできない?)

kenz (2007-06-12 (火) 23:03)

想定していない型が入り込む時点で、それはバグであり、<br>開発者が使用するオブジェクトの性質を正しく把握していないためにおきる問題ではないでしょうか?<br><br>静的型付け言語は型の制約により、<br>コンパイル時や、IDEによるコーディング時にチェックが行われることで<br>オブジェクトの性質を正しく理解しやすいというのがポイントだと思います。<br>インターフェイスの実装が増えた場合メソッド名の重複が起こりえるのは当然ですが、<br>インターフェイスの実装が適切であれば問題はありません。<br>Duck Typingの場合は制約を適切に実装することで、このあたり解決できそうなのですが、<br>そうすると Duck Typingの価値を失うわけで、<br>タイピングを減らしてさくさく開発を優先するか<br>厳密なコーディングで潜在バグを予防するか<br>だと思います。<br><br>インタプリタがドライバーに向かないように<br>Duck Typing自体が小規模開発に適しているので、むりに大規模開発に適用する必要はないのでは?と思いますけど。

まちゅ (2007-06-13 (水) 14:02)

なるほど、インタフェースによる型チェックをするかどうかということですね。<br>Duck Typingに限らず、動的言語か静的言語かの違いのような気もしてきました。<br>Duck Typingならではの問題って何なんだろう…。

kenz (2007-06-13 (水) 15:19)

メソッドの関連性を名前以外で意味的に管理できない点<br>がーとなくものはアヒルに違いない という観点は<br>がーとなくからと、アヒルもダチョウも一緒に出来る反面<br>対象範囲がオフィスまで広がると、シュレッダーもひとまとめにされてしまうおそれがあって<br>オブジェクトを利用しようとしたときに、これはアヒル向けなのかシュレッダー向けなのかが分からないまま動いてしまうのがDuck Typingの問題点ではないでしょうか?