PR
リスト4  動的言語の柔軟性<BR>Rubyのライブラリには,IOとStringIOというクラスが存在する。StringIOはIOと継承関係はないが,ほぼIOと同じ名前のメソッドを備えている。このため,ほとんどの場面でIOと同じように利用できる。例えば本文中に登場したlog_puts()の引数として,StringIOクラスのオブジェクトも指定できる。
リスト4 動的言語の柔軟性<BR>Rubyのライブラリには,IOとStringIOというクラスが存在する。StringIOはIOと継承関係はないが,ほぼIOと同じ名前のメソッドを備えている。このため,ほとんどの場面でIOと同じように利用できる。例えば本文中に登場したlog_puts()の引数として,StringIOクラスのオブジェクトも指定できる。
[画像のクリックで拡大表示]

オブジェクト指向とは密接な関係がある

 現在では,ほぼすべての動的言語がオブジェクト指向機能を提供しています。もちろん静的言語にオブジェクト指向機能がないわけではないのですが,オブジェクト指向と動的言語にはかなり密接な関係があります。

 世界最初のオブジェクト指向言語はSimulaです。Algolの影響を受けた静的型の言語だったのですが,整数などのデータ型を除いたすべてのオブジェクトは「Ref」という一つの型を通じて取り扱われました。どのクラスのオブジェクトであっても,すべてRef型で表現したのです。Ref型のデータについては,それが具体的にどのようなオブジェクトであるかという情報はオブジェクト自身が知っていました。つまり,動的型を採用していたのです。オブジェクト指向プログラミング機能に限っては,その始まりの時点から動的型が用いられてきたのです。

 オブジェクト指向という概念は,このSimulaからSmalltalkに受け継がれました。世の中の多くのオブジェクト指向言語は直接的にはSmalltalkの影響を受けたものが多いのですが,Smalltalkは動的言語です。また,これまた動的言語であるLispにおいてもオブジェクト指向についてのさまざまな試みが行われ,多重継承や今でいうアスペクト指向の基礎などはここで生まれました。歴史的に見れば,オブジェクト指向プログラミングはもともと動的言語とともに発展してきたと言ってもよいでしょう。

 動的言語とオブジェクト指向が密接な関係にあることは歴史上の偶然ではありません。実は,オブジェクト指向の本質が動的性質を要求しているのです。これはオブジェクト指向言語が静的言語であるか,動的言語であるかに関係なく言えることです。

 オブジェクト指向に本質的な概念として「ポリモルフィズム(多義性)」があります。ほかにも「カプセル化」や「継承」も重要だとされることがありますが,よくよく考えるとこの二つはオブジェクト指向の本質とは言えません。事実,カプセル化や継承のないオブジェクト指向言語も存在します。世の中には,継承どころかクラスさえないオブジェクト指向言語だってあるんですから*1

 ポリモルフィズムとは,データ自身が自らにとって最も適切な処理(メソッド)を選択することです。適切な処理は実行時に決まることから「動的結合」と呼ばれることもあります。動的という言葉が含まれていることからも分かるように,ポリモルフィズムは本質的に動的です。プログラムの字面上の型が完全に満たされるならば,実行時の型は字面上の型と必ず一致しますから,その型のデータに対して実行できる処理は一意に決定します。このような条件においては,ポリモルフィズムは実現できないのです。つまりオブジェクト指向言語である限り,静的言語といえども動的な性質から逃れることはできないのです。

動的型が柔軟性をもたらす

 動的な型が提供するのは,ただ単に「宣言を書かなくてよい」というレベルのものではありません。型宣言は,「この変数には必ずこの型のオブジェクトが入る」という制限です。こうした制限は,将来の変化に対して制約となる可能性があります。動的型は型を指定しないことで,将来の変更に柔軟に適応できる可能性を広げていると考えられます。

 動的型が持つこのような柔軟性を表現する言葉として,最近「Duck Typing」という言葉が使われています。これは以下のような西洋の歌の歌詞に由来するものです。

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

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

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

void log_puts(ostream out, char* msg);
出力先outに,引数msgに格納されたメッセージを出力する手続きです。これを次のように呼び出すと,ログをcout(C++の標準出力)に書き出します。
log_puts(cout, "message");

 さて,ここでログの出力先をファイルではなく,メモリー中のバッファに出力したくなったらどうしたらよいでしょうか。出力先を指定する引数outはostreamであると決まっているので,簡単には変更できません。結局は,log_puts()手続き全体をコピーして文字列を出力対象にする別の手続きを用意するか,1次ファイルに出力しておいて,文字列に読み込み直すかしかありません。

 しかし,動的型の言語はこのようなケースに柔軟に適応できる可能性が開けています。動的型言語ではlog_putsのAPIは以下のようになります。

log_puts(out, msg)
動的型ですからプログラム上で型は指定されていませんね。このAPIも,C++の例と同様にSTDOUT(Rubyにおける標準出力)にログを書き出すことを前提としています。

 C++の例と同じように,出力先をメモリー中のバッファに変更したい要求が発生したとしましょう。Duck Typingを採用している場合には話はずっと簡単になります。Duck Typingの原則に従って,「出力先(標準出力)と同じように振る舞うものであれば,それを出力先に使ってもよい」からです。Rubyには,文字列に対してファイルと同じ要領で入出力を行うStringIOというクラスが用意されています。StringIOを使って入出力を実行する例をリスト4[拡大表示]に示します。

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

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

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


まつもとゆきひろ Yukihiro Matsumoto/ネットワーク応用通信研究所 主任研究員

1965年生まれ。鳥取県米子市出身。筑波大学第三学群情報学類卒業。高校時代からのプログラミング言語おたく,オブジェクト指向おたく。1993年からオブジェクト指向スクリプト言語Rubyを開発中。古今東西のプログラミング言語の「良いとこ取り」の性質から海外でも人気上昇中。1997年から株式会社ネットワーク応用通信研究所 主任研究員という立場でRubyの開発をメインの仕事としている。メールアドレスはmatz@ruby-lang.org