図6 Duck Typingの例<BR>RubyのStringIOクラスを使用している。
図6 Duck Typingの例<BR>RubyのStringIOクラスを使用している。
[画像のクリックで拡大表示]
図7 明示的な型チェックを実行した例&lt;BR&gt;Stringクラス以外を受け取った場合,例外を発生させている。
図7 明示的な型チェックを実行した例<BR>Stringクラス以外を受け取った場合,例外を発生させている。
[画像のクリックで拡大表示]
図8 メソッドによる型チェックの例&lt;BR&gt;to_strを持つオブジェクトだけ受け入れている。
図8 メソッドによる型チェックの例<BR>to_strを持つオブジェクトだけ受け入れている。
[画像のクリックで拡大表示]

動的型のメリット

 では,もう一方の動的型についてはどうでしょう。動的型を採用したプログラミング言語の最大の利点は,ソース・コードが簡潔になることです。プログラミング言語はより簡潔により多くのことをコンピュータに伝えるために進化してきました。きちんと動き,エラーも検出できるのであれば,プログラムの動きの本質とは関係のない型指定などない方が良い,というのも一つの考え方です。

 プログラムが簡潔に記述できれば,プログラムを書くときに,型のような処理の本質に関係ない部分のことを考えなくても済みます。本質に集中した簡潔な記述ができれば,生産性も向上することでしょう。

 一方,いくら簡潔に記述できてプログラムが書きやすくなっても,型情報がなければ,プログラムを読解しにくくなるのではないか,という懸念もあります。書きやすくても読みにくければ仕方がありません。これに対しては,処理の本質に集中した簡潔なプログラムは,書きやすいだけでなく読みやすい傾向があると答えられます。実際,動的型の言語(例えばRuby)のプログラムと,静的型の言語(例えばJava)のプログラムを比べると,コード量で数倍の差があることも珍しくありません。多くの人は動的型の言語の方が読みやすいことが多い,と感じているようです。

 簡潔な記述に対しては,プログラムの実行が遅くなるのではないかという懸念もあります。実際,同様の処理を進めるプログラムを静的型の言語と動的型の言語で実行すると,多くの場合,静的型の言語が勝ちます。

 これに対しては,動的型に付き物の実行時の型チェックのコストも影響しているでしょう。さらに,静的型の言語はソース・コードを直接実行できる形式に変換するコンパイル型処理系が多いのに対して,動的型の言語はソース・コードを解釈しながら(一度内部表現に変換してから)実行するインタプリタ型処理系が多いことも一因です。プログラムといっても,一般には実行性能が重要でない局面も多く,コンピュータの性能向上により,そのような領域は増大しているとも言えます。

 動的型のもう一つのメリットは,柔軟性です。動的型の言語で書かれたプログラムでは変数などの型が宣言されていませんから,プログラム開発時に想定していなかったデータを取り扱うことが容易です。この柔軟性のカギになる概念が以下で解説するDuck Typingです。

 動的型の最大のデメリットは,実際に実行してみないとエラーを発見できないことでしょう。静的型の言語がプログラム全体を機械的にチェックできるのに比べると物足りなく感じます。

見かけにこだわるDuck Typing

 動的型の柔軟性を表現する概念がDuck Typingです。これは西洋の格言に由来します。

 If it walks like a duck and quacks like a duck, it must be a duck.(アヒルのように歩き,アヒルのように鳴くものはアヒルに違いない)

 ここから,「アヒルのように振る舞うものは,その実体がなんであってもアヒルと見なす」というルールを引き出すことができます。あるオブジェクトがどのクラスに所属するオブジェクトかは一切考慮せず,どのように振る舞うか(どのようなメソッドを持つか)だけに関心を払うのがDuck Typingです。Duck Typingを言い出したのは「達人プログラマ」として知られるDave Thomasです。

 Duck Typingの具体例を見てみましょう。ファイルにログ・メッセージを出力するlog_puts()という手続きがあるとしましょう。このメソッドは2つの引数(出力先,メッセージ)を取るとします。静的型の言語(例えばC++)であれば次のようなコードになるでしょう。

void log_puts(ostream out, char* msg);

 log_puts()手続きは,出力先outに時刻とメッセージを出力します。次のように呼び出す例を取り上げます。

log_puts(cout, "message");

 例えば,以下のようなログをcout(C++の標準出力)に書き込むことになるでしょう。

2005-06-16 16:23:53 message

 さて,ここでログの出力先をファイルではなく,文字列にしたくなったらどうしましょうか。

 出力先を指定する引数outはostreamであると決まっているので,簡単には変更できません。結局,log_puts()手続き全体をコピーして文字列を出力対象にする別の手続きを用意するか,一時ファイルに出力しておいて,文字列に読み込み直すかしかありません。

 では,Duck Typingを使うとどのように柔軟なコードになるのでしょうか。Duck Typingを使うと,次のようになります。

log_puts(out, msg)

 動的型ですからプログラム上で型は指定していません。C++の例と同様に以下のような呼び出しはSTDOUT(Rubyにおける標準出力)に同じようなログを書き込みます。

log_puts(STDOUT, "message")

 さて,先ほどと同じように文字列に対して出力したくなったとしましょう。Duck Typingでは話がずっと簡単になります。「出力先(標準出力)と同じように振る舞うものであれば,それを出力先に使ってもよい」のです。

 Rubyには文字列に対してファイルと同じように入出力を行うStringIOというクラスが用意されています。StringIOを使って入出力を記述した例を図6[拡大表示]に示します。

 StringIOクラスとSTDOUTのクラス(IO)の間には継承関係がありません。しかし,StringIOクラスはIOクラスの持つすべてのメソッドを備えています。ですから,StringIOクラスはほとんどの局面でIOと同じように使うことができます。

 静的型の言語で同じことをしようと思えば,logを出力するのに必要な「振る舞い」を用意したクラス(Javaの場合,インタフェース)を用意してlog_puts()の最初の引数の型に指定する必要があります。今回の例のように,この型が組み込みの型であったら,「出力先」を表現する別のオブジェクトを新たに作る必要があるでしょう。このような仕組みを最初から用意するのは大変ですし,途中から導入するとなれば,プログラムのあちこちに大規模な改修が必要になるでしょう。

 静的型は,プログラム開発者が型宣言としてたくさんの情報を提供するために,エラーの検出が早く,確実に実行できます。そのかわり,型を設計した時点の前提が変化すると,指定したたくさんの情報(型)を一貫性を保ちながらすべて更新しなければなりません。動的型は,最初からそのような指定を行っていませんから,変化に強い傾向があります。

 では,動的型の言語でDuck Typingを実践するにはどのような指針に従えばよいのでしょうか。基本的な原則はたった1つ,最低限これだけを覚えておけば大丈夫です。

●明示的な型チェックを避ける

 プログラム中で,引数の型をチェックしたくなる場合もあります。例えば,図7[拡大表示]のように,文字列を期待している部分があったとすると,Stringクラスのオブジェクトでなければ例外を発生させてエラーにしたい,という気持ちが自然に生まれてきます。

 しかし,Duck Typingを実践するときには,ここでぐっと我慢する必要があります。クラスを基準としたチェックを行えば,静的型と同様に柔軟性を失ってしまうからです。どうしてもチェックしたい場合でも,「あるクラスのオブジェクトか」ではなく「あるメソッドを持っているか」という条件でチェックしてください(図8[拡大表示])。

 しかしながら,そもそもチェックを行わなくても,期待と違うオブジェクトならば「メソッドが見付からない」というエラーが発生するはずです。

動的型のデメリットを克服

 動的型のデメリットとは大きくまとめると「エラー発見が実行時」,「読解するときのヒントが少ない」,「遅い」の3つになります。

 最初の「エラー発見が実行時」という点は単体テストをきちんとこなすことで克服できます。きちんとテストを進める習慣が定着していれば,コンパイル時の型チェックがなくても信頼性が下がることはありません。

 「プログラムの読解のためのヒントが少ない」点はドキュメントの整備が解決になるでしょう。JavaにはJavaDoc,RubyにはRDocというソース・コード中にドキュメントを同時に記述することでドキュメント維持の負担を減らす技術が存在します。

 最後に,「実行速度が遅い」点ですが,コンピュータの性能がこれだけ向上している昨今,ほとんどのケースで実行効率よりも柔軟性や生産性の方が重要です。

動的プログラミング言語

 現代では,プログラム開発に期待される生産性はますます高くなっています。つまり,今までよりも多くの機能を,今までよりも短い時間で開発することを求められます。

 そのような短期間の開発に対応するため,プログラムを開発しながら,最適な解を模索するような手法が広まりつつあります。これを「shooting a moving target」と呼ぶことがあります。これまでのように,最初にあらゆる状況を考慮して,仕様を決定してから開発に取り掛かるというやり方では対応しきれなくなってきています。手早く開発できること,変化に素早く対応できることがなによりも求められます。

 このような「俊敏さ」を求められる開発では,Duck Typingに代表されるような実行時の柔軟性が非常に役立ちます。Ruby,Python,Perl,PHPなど動的型を持ち,実行時の柔軟性に優れた動的プログラミング言語が注目されているのはこのような理由があるのです。

■変更履歴
図6が別の図と入れ替わっておりました。お詫びして訂正いたします。 [2007/07/24]
図8のなかで「obj.kind_of?」としておりましたが「obj.respond_to?」の誤りです。お詫びして訂正します。図8は修正済みです。 [2008/09/05 00:11]
出典:日経Linux 2005年8月号 125ページより
記事は執筆時の情報に基づいており、現在では異なる場合があります。