継承と多態性
2つ目のクラスは、1つ目のクラスを継承した拡張クラスで、各ファイルのワード出現総数を追跡する機能を追加しています。C++実装では、: public word_countによって継承を行っています。Ruby実装では、これを< WordCountで表現します。どちらの言語でも、この拡張クラスでは、各ファイルにおける出現数を格納するハッシュマップを追加しています。addメソッドは、このハッシュマップの更新を行うように拡張されています。ハッシュマップの情報はfile_occurrencesメソッドによって返されます。
継承に関しては、C++とRubyの間に重要な違いはほとんどありません。C++とは異なり、Rubyは多重継承をサポートせずにミックスイン(mixin)をサポートします。リスト2の16行目のinclude Comparableがその例です。この行ではComparableというモジュールをincludeしています。モジュールは一連の関数定義です。モジュールのインスタンスを作成することはできません。可能なのはそれをクラス定義に含めることだけです。Comparableモジュールは、比較演算子(<、>、==)を定義していますが、<=>演算子は定義していません。そこで、この例ではdefにより<=>を定義するとともにComparableモジュールを含めることで、<=>演算子を使用できるようにしています。
C++では、継承と仮想関数を組み合わせて多態性を実現することができます。T *型のポインタxは、T型のオブジェクト、またはクラス階層でTよりも下位の型のオブジェクトを指すことができます。xを通じて呼び出された仮想メソッドは、このクラス階層において、xが指しているオブジェクトの型から順にさかのぼることで解決されます。
一方、Rubyでは、ダックタイピング(duck typing)という方法を使用します。ダックタイピングとは、「あるオブジェクトがアヒル(duck)のように歩きアヒルのように話すなら、Rubyのインタプリタはそのオブジェクトをアヒルとみなす」ということです。たとえば、次のコードを考えてみましょう。
def my_method(x) x.print_hello end
Rubyの場合、xの型が何であっても構いません。オブジェクトxにprint_helloがあれば、このコードは動作します。C++では、共通の基本型から派生したオブジェクトが必要になりますが、Rubyでは、特に関連性のない型のオブジェクトでも、それがprint_helloを実装してさえいれば、my_methodに渡すことができます。
視認性とアクセスコントロール
3つ目のクラスは、リスト1では67行目から、リスト2では54行目から実装されています。このクラスは、指定されたディレクトリ内にあるすべてのファイルに繰り返し適用され、ファイルをトークンに分割して、各ワードの出現数をカウントします。また、XML出力をダンプするメソッドを定義します。
C++とRubyは両方とも、パブリックメンバ、プロテクトメンバ、およびプライベートメンバをサポートします。今回のサンプルでは、どちらの実装でもcount_words_in_fileメソッドをプライベートとして宣言しています。
パブリックメソッドは誰でも呼び出すことができ、プロテクトメソッドは、同じクラスのオブジェクト、または定義クラスから継承されているオブジェクトのみが呼び出せます。この2つについては、C++とRubyの間に違いはありません。しかし、プライベートメソッドの動作はC++とRubyでは異なります。C++では、プライベートメソッドはクラスに対してプライベートですが、Rubyではインスタンスに対してプライベートです。つまり、Rubyでは、プライベートメソッド呼び出しのレシーバを明示的に指定できません。
ブロックとクロージャ
Rubyがサポートする機能で、C++にはないものに「ブロック」があります。ブロックの使用で最も一般的なのがイテレーションです。ブロックのイテレーションの例は、リスト2の65行目、73行目、および76行目のWordCounterクラスの実装で確認できます。
たとえば、65行目のコードを見てみましょう。
Dir.foreach(".") { |filename|
count_words_in_file filename
}
ここでは、ディレクトリを調べるためにDirクラスを使用しています。foreachメソッドは2つの引数をとり、この例では文字列"."と、カッコに囲まれたコードブロックを渡しています(コードブロック内の垂直バーの間には、さらに引数filenameを指定しています)。foreachメソッドはブロックを繰り返し呼び出して、現在の作業ディレクトリ"."で見つかった各ファイルの名前を渡します。このシンプルな機能は、キー入力の手間を省くだけでなく、コードを非常に読みやすいものにしています。
また、イテレーション以外でもブロックが役立つ場面があります。48行目では、ブロックを使用して、ペアの配列をソートするための比較関数を指定しています。
def file_occurrences return @file_hash.sort { |x,y| -(x[1]<=>y[1]) } end
演算式x <=> yは、x < yの場合は-1、x == yの場合は0、そして、x > yの場合は1になります。上記のコードは、StringとFixNumから成るペアの配列を返します。このブロックでは、各ペアの2番目の要素(FixNum)を、<=>演算子の否定を使用して比較することを指定しています。したがって、このメソッドは、ワード出現のペアを出現数が多いものから順に返します。
C++の実装を示すリスト1の15行目でも、ペアの順序付けに使用する比較関数を指定しています。ただし、匿名関数はサポートされていないため、この比較は関数オブジェクトとして実装しています。この関数は、25行目でSTL multisetの定義に使用します。
Rubyのブロックが便利なのは、1つにはそれがクロージャであるからです。クロージャは自分が定義されているコンテキストを取得するので、定義の範囲内で見つかったローカル変数を参照できます。リスト2の76行目のコードを見てください。
wc.file_occurrences.each { |pair|
f = e.add_element "file", {"occurrences"=>"#{pair[1]}"}
f.add_text pair[0]
}
演算式wc.file_occurrencesはペアの配列を返します。その後、この配列のeachメソッドが、後続ブロックを引数として呼び出されます。注意してほしいのは、このブロックがeachメソッド内から呼び出されるということです。ただし、ブロックはクロージャであるため、そのブロックが定義されているメソッドのローカルスコープ内にあるオブジェクトe(XML要素)には引き続きアクセスできます。
ここで説明した機能はC++関数オブジェクトで実装できるものがほとんどですが、ブロックによって正確性と読みやすさが向上することは、ここまでの説明でおわかりでしょう。
広汎なライブラリと正規表現
RubyがC++よりも明らかに優れている点は他にもあり、1つは標準ディストリビューションに膨大なライブラリコレクションが含まれていることで、もう1つは正規表現をサポートしていることです。たとえば、リスト1のdump_resultsメソッドにあるXMLジェネレータのアドホック実装と、リスト2のREXMLライブラリの使用を比較してみてください。また、C++実装でのディレクトリ操作における擬似標準dirent.hの使い方と、Ruby実装でのDirクラスの使い方を比べてみてください。さらに、リスト1の77行目で行っているファイルアドホック解析と、リスト2の90行目のかなり簡略化されたコードを比較してください。
Boostなど多くのC++ライブラリには広汎なユーティリティが用意されていますが、Rubyでは、これらの機能の多くが標準ディストリビューションの一部として提供されています。したがって、Rubyプログラム言語とその標準ライブラリがあれば、C++と比べ多くの一般的なプログラミングタスクを簡素化できます。
言語機能の概要
表1に、この記事で説明したC++とRubyの重要な機能をまとめます(比較のためにJavaの機能も記載もします)。
詳細情報
Rubyが提供する機能とライブラリについては、とても1回の記事では説明しきれません。しかし、この小さなサンプルの中にも、今後のプロジェクトに応用できるアイデアがいくつかあったのではないかと思います。Rubyという強力かつシンプルなプログラム言語についてもっと詳しく知りたい方は、インターネット上のさまざまなリソースを参照してください。まずは、Ruby-Lang.orgとRuby-Doc.orgから始めることをお勧めします。

