はじめに
Java開発者がRubyを学ぶ理由は何でしょうか。Rubyはその多様性と柔軟性でJavaを見事に補完してくれるので、両方の言語を使うことで、より効率的で効果的な開発ができるためです。実際、私は開発にはすべてJava、Ruby、Common Lispを使っていますが、その中でもRubyが開発の中心になりつつあります。特に、Java開発者がRubyを好む理由は次のようなものです。
- Rubyはスクリプト言語なので、小規模なプロジェクトに向いている。データ変換やテキスト処理用のユーティリティをすばやく作成する必要がある場合、私は必ずと言っていいほどRubyを使用している。
- Rubyは動的かつシンプルな言語である。
- Rubyを使うと、違った視点から問題が解決することがよくある。
- JRubyはまだ開発段階だが、Java VMベースのすぐれたRuby開発プラットフォームとなると思われる。現在、IntelliJ、NetBeans、EclipseのいずれもRubyを強力にサポートしている。
- 通常、ソフトウェアのメンテナンスはコードの行数に比例してコストが増加するが、Rubyは短く簡潔なプログラムなので、他のプログラムに比べて読みやすく、内容を理解するのもメンテナンスを行うのも簡単である。
- Ruby on RailsによるWeb開発フレームはバックエンドにデータベースを配置した、中小規模のWebアプリケーションに最適であるが、Ruby on Railsを利用するにはRubyの知識が必要である。
この記事では、RubyがJava開発者と非常に相性が良い理由を示すために、開発をより効率的にする言語的な特長を紹介し(表1「RubyとJavaの特長の比較」を参照)、両方の言語の短いサンプルプログラムを紹介します。
表1 RubyとJavaの特長の比較
| 言語機能 | Ruby | Java |
| 文字列操作 | 可 | 可 |
| 全クラスの継承 | 可 | final宣言をされたクラスを除く |
| ダックタイピング | 可 | 不可 |
| ブロックコード | 可 | 不可 |
| 正規表現 | ネイティブサポート | 標準ライブラリによるサポート |
| 外部プログラムの使用 | 可 | 可。ただしRubyほど簡単ではない。 |
| ネットワークプログラム | 標準ライブラリにおるサポート | 標準ライブラリによるサポート |
| 型付け | 動的 | 静的 |
| 継承 | mix-insによる多重継承をサポート | シングル |
必要なもの
この記事を読み進めるには、外部のRubyライブラリをインストールする必要があります。RubyGemsライブラリシステムをインストールすると、この作業を簡単に行うことができます。RubyGemsをRubyForgeからダウンロードして、ご使用のオペレーティングシステムのインストール手順に従ってインストールしてください。既にRubyがセットアップされている場合、RubyGemsが含まれているかを確認してください(多くのRubyインストールパッケージにはRubyGemsが含まれています)。インストールされているかを確認するには、コマンドシェルでgemと入力します。ライブラリを一括管理できる保管場所とRubyGemsなどの標準ツールがあれば、必要なライブラリを探し、それをインストールして複数のプロジェクトで使うといった作業が必要なくなり、多くの時間を節約できます。
以下のコマンドを使って必要なgemsをインストールします。
gem query --remote # if you want to see all available remotely
# installable gems
sudo gem install activerecord
sudo gem install mysql # if you want to use MySQL
sudo gem install postgres-pr # optional: install "pure ruby"
# PostgreSQL interface
sudo gem install postgres # optional: install native PostgreSQL
# interface
sudo gem install ferret # a search library like Lucene (same API)
sudo gem install stemmer # a word stemming library for demonstrating
# extending a class
gem query # to show gems locally installed
gem specification activerecord # info on gem
# (ActiveRecord in this example)
編集部注
上記コマンドにはコメントが記述されていますが、これはコマンドの解説をするために記載されているものであり、実際のコマンド入力時には記述不要です。なお、2行にわたっているコメントがいくつかありますが、これはページの都合によるものであり、原文では1行で記述されています。
Mac OS XやLinuxでは、sudoを使ってgem installを実行する必要がありますが、Windowsユーザーの場合は最初に「sudo」と入力する必要はありません。
また、この記事では、次のようにRuby irbシェルを使用します。この記事を読み終えるまでは、Ruby irbシェルを開いたままにしておいてください。
markw$ irb
>> s = "a b c"
=> "a b c"
>>
サンプルプログラムとコード例は、コピーしてirbインタラクティブセッションに貼り付けることができる程度に短くしています。
Rubyの文字列操作
RubyのStringクラスには、Javaの文字列操作機能よりも柔軟な数多くの文字列操作用メソッドが用意されています。この節では、Rubyの便利な文字列操作の一部を紹介します。以下のコード例は、文字列の連結、スライスを使った文字列の分割、サブストリングの検索の各操作を示しています(「#」の右側の文字は行末までプログラムコメントになります)。
# use the "pretty print" library. Defines the function ’pp’
require ’pp’
# define some strings to use in our examples:
s1 = "The dog chased the cat down the street"
s2 = "quickly"
puts s1
# a substring slice up to and including character at index==6
puts s1[0..6]
# a substring slice up to (but not including) the character at index==6
puts s1[0...6]
# expressions inside #{} are inserted into a double quote string
puts "He is a #{s2} dog #{1 + 6} days a week."
# create a copy of the string: the new copy has white space removed
puts " test ".strip
# string literals can also be formed with single quotes
puts s1 + ’ ’ + s2
puts s2 * 4
# find index (zero based) of a substring
puts s1.index("chased")
# replace a substring (/dog/ -> /giant lizard/)
s1[4..6] = ’giant lizard’
puts s1
# the << operator, which also works for arrays and other collections,
# copies to then end
s2 = s2 << " now"
puts s2
puts "All String class methods:"
pp s1.methods # the method "methods" returns all methods for any object
出力結果は次のようになります。
The dog chased the cat down the street
The dog
The do
He is a quickly dog 7 days a week.
test
The dog chased the cat down the street quickly
quicklyquicklyquicklyquickly
8
The giant lizard chased the cat down the street
quickly now
All String class methods:
["send",
"%",
"index",
"collect",
"[]=",
"inspect", ....] # most methods not shown for brevity--try this in irb
上の例の<<演算子は、実はメソッド呼び出しです。式の評価時、Rubyでは中置演算子はメソッド呼び出しに変換されます。例えば、次のコードの<<演算子は、右側の式の値を左側の式の値に追加します。
>> "123" << "456"
=> "123456"
>> "123".<<("456")
=> "123456"
>> 1 + 2
=> 3
>> 1.+(2)
=> 3
上の例の「.<<」という書き方は、メソッド呼び出しの標準書式です。
多くのクラスでは、<<演算子を使ってオブジェクトをクラス固有のコレクションに追加します。例えば、後述のFerret検索ライブラリ(先ほどインストールしたRuby gem)を見ると、インデックスにドキュメントを追加するために<<演算子がどのように定義されているのかが確認できます。
既存のクラスの編集
Rubyの多様性のポイントは、Rubyのすべてのクラスをメソッドとデータを追加して拡張できるという点です。私はよく、コアRubyクラスのオリジナルのソースコードを拡張するのではなく、自分が開発しているアプリケーション内でクラスを拡張するという方法をとります。JavaやC++開発者にとっては奇妙に思えるかもしれませんが、このテクニックを使うと、プロジェクトのリソースを1か所で管理でき、オリジナルのクラスを「膨張」させることなく多くの開発者がアプリケーション固有の機能を追加できるようになります。Javaプログラマの立場で、Javaのルールによる制約を考えてみてください。Javaでは既存のクラスに機能やデータを追加する場合、サブクラスを作成する必要があります。
次のリストは、Stringクラスにstemメソッドを追加する方法を示しています。
begin
# stem is undefined at this point
puts "The trips will be longer in the future".downcase.stem
rescue
puts ’Error:’ + $!
end
require "rubygems"
require_gem ’stemmer’
class String # you will extend the String class
include Stemmable # add methods and data defined in module Stemmable
end
puts "The trips will be longer in the future".downcase.stem
また、自分のアプリケーション内で、既存のクラスにメソッドや新しいクラスインスタンス変数を追加できると便利なことが分かります。
次の節では、Rubyの強力な柔軟性を証明するもう1つの例である「ダックタイピング」(duck typing)について説明します。
Rubyのダックタイピング
Javaでは、オブジェクトのクラス階層で(publicやpackageなどの可視性を指定して)定義したオブジェクトのメソッドしか呼び出せません。例えば、あるオブジェクトのコレクションがあり、そのコレクション内の各要素に対して、1つ以上のメソッドを呼び出す処理を繰り返す場合を考えてみましょう。Javaでこのような処理を行う場合、対象となるオブジェクトは同じクラス階層に属しているか、呼び出すメソッドを定義しているインターフェイスを実装している必要があります。
話の流れから予想はついていると思いますが、RubyはJavaよりもはるかに柔軟です。Rubyのランタイムのメソッド呼び出しスキームでは、固有のデータ型やクラスは必要ありません。次のように、objオブジェクトのfooメソッドを呼び出し、この最初のメソッド呼び出しによって取得したオブジェクトからbarメソッドを呼び出すとします(この例は2つの同じ呼び出しを示しています。メソッドに引数がない場合、()は省略できます)。
obj.foo.bar
obj.foo().bar()
obj.fooを呼び出した結果はオブジェクトになります。そして、この新しいオブジェクトのクラスが何であろうと、そのオブジェクトのbarメソッドを呼び出すことになります。
もう1つの例として、コレクション内の各オブジェクトからnameというメソッドを呼び出すことを考えます。このコレクション内の1つの要素が、何らかの理由で、nameメソッドが定義されていないMyClass2クラスのインスタンスになったとしましょう。その場合、このオブジェクトからnameメソッドを最初に呼び出そうとした時点で、ランタイムエラーが発生します。このエラーは、次のようにメソッドを動的に追加することで解決できます。
class MyClass2
def name
"MyClass2: #{this}"
end
end
Javaのような強力な型チェックを持つ言語に慣れている開発者は、コンパイラやインタプリタがすべての型の使用を静的にチェックしない、このような「安全性に欠ける」柔軟性はプログラムの信頼性を低下させると思うようです。しかし、ランタイムの型チェックで発生するプログラムのバグは、テストですばやく検出できるので、ソフトウェアの信頼性は低下しません。それよりも、言語がより柔軟であるというメリットによって、プログラム自体の長さも開発時間も短くできます。
不明なメソッドの処理
ダックタイピングなんて信頼できないとまだ思っているでしょうか? ではもう1つ、Rubyの裏技を紹介しましょう。Rubyクラスで不明メソッドを処理する方法です。以下は、2つのメソッド(定義済みのlengthと未定義のfoobar)を文字列オブジェクトに適用する簡単な例です。
markw$ irb
>> s = "this is a string"
=> "this is a string"
>> s.length
=> 16
>> s.foobar
NoMethodError: undefined method `foobar’ for "this is a string"
:String from (irb):3
未定義のメソッドの場合、エラーがスローされます。そこで、独自に作成したmethod_missingメソッドをStringクラスに「貼り付け」ます。
>> class String
>> def method_missing(method_name, *arguments)
>> puts "Missing #{method_name} (#{arguments.join(’, ’)})"
>> end
>> end
=> nil
>> s.foobar
Missing foobar ()
=> nil
>> s.foobar(1, "cat")
Missing foobar (1, cat)
=> nil
>>
Rubyのランタイムシステムは、オブジェクトのメソッドを検出できない場合、必ず初期設定で継承されているmethod_missingを呼び出し、単にNoMethodError例外を発生させます。この例では、この継承されているメソッドをエラーをスローしないメソッドでオーバーライドし、メソッド呼び出しの名前と引数を出力しています。次の例では、このメソッドをさらに再定義しました。今度は、メソッド名がfoobarであるかどうかを確認するためのチェックを定義しています(メソッド名は評価時にto_sを使って文字列に変換しています)。
>> class String
>> def method_missing(method_name, *arguments)
>> if method_name.to_s==’foobar’
>> arguments.to_s.reverse # return a value
>> else
?> raise NoMethodError, "You need to define #{method_name}"
>> end
>> end
>> end
=> nil
>> s.foobar(1, "cat")
=> "tac1"
>> s.foobar_it(1, "cat")
NoMethodError: You need to define foobar_it
from (irb):38:in `method_missing’
from (irb):43
from :0
>>
メソッド名がfoobarに等しい場合は戻り値が返され、等しくない場合はエラーがスローされます。
Rubyのコードブロック
Rubyでは、データの繰り返し処理を行う新たな方法としてコードブロックが用意されています。このコードブロックは、Java言語の制限された繰り返し機能よりもはるかに柔軟で強力な機能です。前述の基本的な文字列操作の例では、stemmer gemを使って英単語が含まれる文字列の語幹を検索しました。次の例では、Stringクラスのsplitメソッドを使って文字列を分割し(区切り文字には空白文字を使用)、コードブロックの始まりと終わりを示す { と } で定義されたコードブロックを渡します({ と } の代わりにbeginとendを使うこともできます)。2つの | で囲まれた文字列はブロック内のローカル変数を表します。
puts "longs trips study studying banking".split(’ ’)
puts "longs trips study studying banking".split(’ ’).each
{|token| puts "#{token} : #{token.stem}"
このコード例を実行すると、以下の結果が得られます。
longs
trips
study
studying
banking
longs : long
trips : trip
study : studi
studying : studi
banking : bank
さらに、コードブロックの有効な使用例を以下に示します。この例では、Arrayクラスのcollectメソッドを使用しています。collectメソッドは配列の各要素を処理し、その要素をコードブロックに渡します。
require ’pp’
pp ["the", "cat", "ran", "away"].collect {|x| x.upcase}
pp ["the", "cat", "ran", "away"].collect {|x| x.upcase}.join(’ ’)
この例では、コードブロックは要素が文字列であると想定して、各要素のupcaseメソッドを呼び出しています。collectメソッドは収集した結果を新しい配列に返します。また、結果として得られた配列の要素を、空白文字を要素の区切りに設定したjoinメソッドを使って1つの文字列に連結しています。この出力結果は次のようになります。
["THE", "CAT", "RAN", "AWAY"]
"THE CAT RAN AWAY"
コードブロックを使用するメソッドの作成
yieldメソッドを使うと、メソッドや関数の呼び出しに渡されたコードブロックを呼び出すことができます。次の例では、block_given?メソッドを使って、コードブロックが指定された場合にyieldを呼び出しています。yieldメソッドは出力される値を返します。
def cb_test name
puts "Code block test: argument: #{name}"
s = yield(name) if block_given?
puts "After executing an optional code block, =#{s}"
end
以下の例ではcb_test関数を2回呼び出していますが、1回目はコードブロックを指定する場合を、2回目はコードブロックを指定しない場合を示しています。
>> puts cb_test("Mark")
Code block test: argument: Mark
After executing an optional code block, =
nil
=> nil
>> puts cb_test("Mark") {|x| x + x}
Code block test: argument: Mark
After executing an optional code block, =MarkMark
nil
=> nil
>>
文字列値「Mark」は引数としてyieldに渡され、コードブロックの内部でローカル変数xに値「Mark」が割り当てられます。その結果、コードブロックの戻り値は「MarkMark」になります。
Rubyの正規表現
Rubyでは、組み込みのRegexpクラスを使って正規表現を処理することができます。Javaのjava.util.regex APIも同じような機能を備えていますが、Rubyの正規表現サポートの方が明らかに進んでいます。正規表現オブジェクトは、Regexp.new("[a-e]og")のようなメソッド呼び出しを直接使っても、/[a-e]og/のようなスラッシュで正規表現を囲む形式を使っても作成できます。正規表現とRubyの正規表現サポートについては、よくできたチュートリアルがWeb上でいくつも公開されています。以下に、=~演算子のみを使用した簡単な例を示します。
=> 4
>> "the dog ran" =~ /[a-e]og/
=> 4
>> "the zebra ran" =~ /[a-e]og/
=> nil
Rubyのネットワークプログラミング
Rubyにはネットワークプログラミング用の標準ライブラリも用意されています。詳しくは、私が執筆した以前の記事を参照してください。私はRubyを使って、インターネットからデータを収集し、そのデータを解析してXMLやデータベースに格納するという作業をよく行います。
RubyのFerretライブラリによるドキュメントのインデックス作成と検索
現時点で、Ferretと呼ばれるRuby gemが既にインストールされているはずです。Ferretは、Java Luceneをベースとした高速のインデックス作成および検索ライブラリです(Common LispのバージョンMontezumaよりもはるかに高速です)。Ferretライブラリは、その開発において、作者であるDavid BalmainがそのほとんどをCで開発しRubyでラップしたというおもしろい経緯があります。これは、つまり、Rubyで開発を始めてパフォーマンスに問題が生じた場合は、処理速度が重視される部分を常にCまたはC++で再コーディングできるということです。Ferretでは、Rubyを使用した場合に自分のアプリケーション内で使えるクラスがいくつか定義されています。以下に例を示します。
- Field
ドキュメントを格納するデータ要素を表します。Fieldにはインデックス付きとインデックスなしがあります。通常、私は単一インデックスを持つ(つまり検索可能な)テキストフィールドを使用し、その後でインデックスのない複数の「メタデータ」フィールドを使用します。オリジナルファイルのパス(WebのURLなど)は、インデックスなしのフィールドに格納できます。
Microsoft Word文書のインデックス作成と検索
以下は、Microsoft Word文書を読み取ってプレーンテキストを抽出するために私が使用しているRubyクラスです。Rubyで外部プログラムを使用する例として紹介します。
class ReadWordDoc
attr_reader :text
def initialize file_path
# back quotes to run external program
@text = `antiword #{file_path}`
end
end
この「裏技」は、私がオープンソースのAntiwordユーティリティを使って実際にWord文書ファイルを処理しているものです。外部コマンドをバッククォートで囲むと、外部プログラムを実行してその結果を文字列に出力することができます。LinuxまたはOS Xの場合は次のコマンドを実行してください(Windowsの場合は`dir`)。
この例では、外部コマンドls(Unixのディレクトリ表示コマンド)を実行した結果が出力されます。
次のRubyスクリプトを実行すると、Word文書がインデックスに入力されます(プレーンテキストファイルはより簡単です。練習として試してみてください)。
require ’rubygems’
require ’ferret’
include Ferret
include Ferret::Document
require ’read_word_doc’ # read_word_doc.rb defines class ReadWordDoc
# any path to a directory
index = Index::Index.new(:path => ’./my_index_dir’)
# path to a Microsoft Word
doc_path = ’test.doc’
# get the plain text from the Word file
doc_text = ReadWord.new(doc_path).text
doc = Document.new
doc << Field.new("doc_path", doc_path,
Field::Store::YES, Field::Index::NO)
doc << Field.new("text", doc_text,
Field::Store::YES, Field::Index::TOKENIZED)
index << doc
# a test search
index.search_each(’text:"Ruby"’) do |doc, score|
# print doc_path meta data
puts "result: #{index[doc][’doc_path’]} : #{score}"
# print original text
puts "Original text: #{index[doc][’text’]}"
end
index.close # close the index when you are done with it
このサンプルコードがいかに短いか分かると思います。Word文書からテキストを抽出するAntiwordを使うためのクラスを含めても、わずか24行で、Wordからテキストを抽出してインデックスを作成し、検索を実行して終了したらインデックスをクローズするというサンプルを作成できるのです。
Rubyを使うと、複雑な作業をほんの数行のコードで処理することができます。Javaでこのようなサンプルコードを作成した場合、(私が作った)非常によくできたLuceneライブラリを使ったとしても、行数ははるかに長くなるでしょう。プログラムは短ければ短いほど、メンテナンスが簡単になり、そのコストも低減されます。
この例では、Word文書を使いましたが、OpenOffice.org文書も読み取り可能です。約30行の純粋なRubyコードで、ドキュメントをunzipし、unzipされたXMLデータストリームのcontent.xml要素からテキストを抽出できます(RubyではXMLも簡単に処理できますが、本稿では触れません)。
RubyによるJavaの補完
通常、企業のIT予算においてソフトウェアの開発とメンテナンスにかかるコストは莫大なものです。サーバやインターネット接続などにかかるコストの比ではありません。しかし、Rubyを使うことで、通常はプログラムの長さ自体が短くなるため、システムの構築およびメンテナンスのコストを大いに削減できます(私の場合、コード1行にかかる時間は、どのプログラミング言語でもほとんど差はありません)。
では、Javaはどのような場面で使えばよいのでしょうか? 私の場合は、10年以上コンサルティングしているお客様のシステムを構築するときにJavaプラットフォームを使用したので、これからも確実にJavaを使い続けるでしょう。堅牢なJavaベースのWebアプリケーションは、少なくともサーバがダウンしたりハードウェアメンテナンスで再起動したりする場合以外は、いつまでも稼働するでしょう。実際に数か月間も無人状態で問題がなく稼働するシステムを確認しています。
私の意見としては、大規模なシステムのサーバサイドではJavaを使い続け、小規模なユーティリティプログラムでRubyを使い始めるのが良いと思います。私は、JavaとRubyは互いに競合するのではなく、互いに補完するものと考えています。作業に合わせて最適なツールを使うことをお勧めします。