JRubyはJavaプラットフォーム用の代替言語で、Yukihiro Matsumoto氏(通称「Matz」)が開発したプログラミング言語であるRubyに基づいています。RubyCentralのホームページ(参考文献)にあるように、Rubyは「古典的オブジェクト指向言語Smalltalkの持つ純オブジェクト指向の力と、Perlのようなスクリプト言語の持つ表現力と便利さを組み合わせた」プログラミング言語です。すっきりとして直感的な構文や意味体系、透明で開発者に使いやすいプログラミング・モデルはよく知られており、また比較的成熟した言語です。
JRubyはRubyインタープリターの純Java実装です。このシリーズで取り上げる大部分の言語と同様、JRubyは強力であり、しかも簡単に学ぶことができます。JRubyはPerlの持つ高度なテキスト処理や、Groovy開発者にはおなじみのイテレーターやクロージャー、またこのシリーズで取り上げたJythonなどの言語の持つ、高速開発機能を取り入れています。JRubyもインタープリター型言語であり、従ってコマンドラインからの実行や、単純表現やコード・ブロックの評価をその場で行うことができます。
JRubyはあらゆる人のあらゆる用途に使えるものではありませんが、それまでの言語で持っているような重要な機能を確実に利用しており、強力でありながら構文的に単純な言語となっています。JRubyはこのシリーズで取り上げた大部分の言語とは異なり、一つのパッケージの中に強力な機能の大部分を取り込んでいます。例えば、JRubyはイテレーターとテキスト処理機能を組み合わせているため、強力かつ直感的なパーサーが比較的簡単に書けるようになっています。また、ファンクションがファーストクラス・データ型として使えるようになっています。そのためこれをブロックやクロージャーと組み合わせることによって、普通はループ構造体やもっと伝統的なイテレーター・クラスに任されるような、些末な仕事の大部分を処理するようにクラスを拡張することができます。
従って自然なこととして、JRubyはJavaプラットフォームとも緊密に統合されており、Javaクラス・ライブラリーの便利さと柔軟性を取り入れています。JRubyはモジュールやクラスをサポートしているので、スケーラブルで堅牢なアプリケーション開発に適しています。JRubyはJavaクラスをスクリプト記述するために使ったり、(インタープリターを通して)直接Javaアプリケーションに埋め込んだりすることができます。RubyもJRubyも共にオープンソースの言語であり、無料で開発や展開に使用することができます。
alt.lang.jreシリーズ3回目の今回の記事では、JRubyを集中的にとり上げます。最初は基本的なインストールの仕方を説明し、その後で、なぜJRubyがJava開発ツールボックスに加える価値のあるものなのかを説明します。残りはコード中心の説明になります。簡単な例から始め、学習しながら次第に複雑な例に移って行きます。ソースコードは、このページの一番上または一番下にあるCodeボタンをクリックしてダウンロードしてください。
この記事の執筆時点では、JRuby v0.7.0はSourceForgeからダウンロードすることができます。アーカイブをダウンロードしたら、適当なディレクトリーで解凍します(最上位レベルのjruby-0.7.0ディレクトリーはアーカイブの一部ですので注意してください)。JRuby はWindowsプラットフォームではバッチ・ファイルから実行し、UnixプラットフォームではBashシェル・スクリプト・ファイルから実行します。どちらもJRubyのbinサブディレクトリーにあります。JRubyを起動するには、単純にOSのプロンプトでjrubyとタイプします。
そうすると、次のようなものが見えるはずです。
using JRUBY_BASE ... using JRUBY_HOME ... using JAVA_HOME ... using CLASSPATH ... using ARGS ... |
コードを実行せずにこのインタープリターを終了するにはCtrl-Cを押します。インタープリターを抜けて入力したコードを実行するには、Ctrl-Zを押します。
このシリーズで取り上げる他の言語と同様、JRubyはファンクションをファーストクラスのデータ型としてサポートしています。つまりファンクションは変数に割り当てることができ、コレクションの中に置くことができ、他のファンクションとの間でやり取りすることができます。またJRubyはインタープリター型で動的型付け言語(dynamically-typed language)なので、動的な処理(on-the-fly processing)に最適です。JRubyの便利な機能としては、次のようなものが挙げられます。
- 簡単な構文:JRubyには(Javaのセミコロンのような)ステートメント終了子がなく、引き数を持たないファンクション・コールでは、括弧を省くことができます。
- 変数には型がなく、従って宣言の必要がありません。
- クラスのメンバー属性は読み取り可能として、かつ/または書き込み可能として規定でき、名前でアクセスすることができます(つまり、ゲッターやセッターを使う必要がありません)。
- ブロックやイテレーターがあるために、特に集合のメンバーに対する繰り返し評価が容易になります。
- ストリング表現や正規表現機能が充実しているため、テキスト処理が非常に単純です。
このような機能(そして他にも機能があります)がJavaプラットフォームの力と組み合わされることによって、JRubyはJava開発者にとって素晴らしい代替言語となります。
これから先では、コード例を学び、それを解説して行くことにします。最初の例として、典型的なHello WorldプログラムをJRubyがどのように処理するか、下記で見てみましょう。
print "Hello, World!\n" |
JRubyでは、この単純なプログラムを2つの方法のどちらかで処理することができます。JRubyインタープリターを起動してコマンド・シェルに入れ、その後にファイルの最後を示すCtrl-Zを付けるか、あるいはこれをHelloWorld.rb のようなJRubyファイルに入れ、次のようなコマンドを実行します。
jruby HelloWorld.rb |
ご想像の通り、どちらでも出力は同じです。
Hello, World! |
さて、Hello World!で私達の自己紹介を済ませたので、リスト1に示すような、もう少し複雑なプログラムに進みましょう。
リスト1. my publicメソッドを表示する
x = 3
for m in x.methods
print "#{m} " if not methods.index(m)
end
|
このコードで最初に気がつくのはmethodsメソッドです。これは、それを呼び出したオブジェクト(つまり「x」)のクラスのパブリック・インスタンスの全メソッドを、そのオブジェクトのクラスの、全てのスーパークラスのメソッドと共に返します。次にforループは、JRubyのクラスInteger(そのうち数字3はインスタンスです)で使える全てのメソッドまで繰り返します。もしクラスObjectに対して返されるmethodsリストにそのメソッド名が現れても、出力はされません。このすぐ後で見るように、インスタンスとドット( . )演算子を前につけずに任意のメソッドを呼び出すと、Objectクラスにメソッドを呼び出します。ですからif修飾子にあるmethodsメソッドはObjectクラスに呼び出されています。
JRubyのnil値は(Java言語でのnullと同様)条件表現でのfalseに等しく、従ってテストmethods.index(m) == nilが実際に評価される表現だということには注意してください。括弧の間にある表現の評価結果を出力するには、printコール内部では#{...}構文を使います。
このプログラムの出力はリスト2のような、メソッドのリストになるはずです。
リスト2. my publicメソッドのリスト出力
to_str / ~ id2name ^ to_f + [] ** - >> | to_i << & size
* % integer? downto succ next times upto step chr member?
include? sort each_with_index collect find_all inject sort_by max entries all?
select reject grep min any? partition group_by detect map find abs
-@ round zero? floor modulo <=> ceil coerce +@ truncate remainder nonzero?
divmod > >= between? < <=
|
この、ちょっとしたプログラムが返すメソッド名の幾つかに皆さんは驚くかも知れません。例えば、[]演算子という一つのメソッドは、数字のバイナリー表現の添え字(subscript)で規定された位置のビットを返します。ですから、x[0] == 1やx[1] == 1、x[2] == 0のようなものになります。このプログラムはまた、+ や- などの典型的な数式表現、それに> や< のような条件演算子も返します(downtoやstepなど、このリストにある他のメソッドについては、すぐ後で触れることにします)。
Groovyと同様JRubyは、数字であれストリングであれ、クラス、モジュール、ファンクションであれ、それにリテラルまで、全てをオブジェクトとして扱います。実際、インタープリターを呼び出してみても、自分がmainというオブジェクトの中にいることが分かるでしょう。これは、プロンプトから次のようにタイプすることで分かります。
print inspect, "\n" |
インタープリターはmainで応答しますが、それはinspectメソッドの結果でprintメソッドを呼び出す結果によるものです。
printメソッド、inspectメソッドのどちらもObjectクラスのメンバーであり、全てのJRubyオブジェクトはObjectクラスから継承します(これはjava.lang.Objectクラスと同じではありませんので、注意してください)。JRubyにある全てのものはクラスObjectから継承するので、どのオブジェクトのどのインスタンスも、膨大な数の一連のメソッドにアクセスすることができます。ですから、JRubyはprintステートメントを持っていませんが、printメソッドはどこからでもアクセスできるので、まるでprintステートメントを持っているように見えます。(もちろん、printをステートメントではなくメソッドとして使うのは、Javaプログラマーにはお馴染みのものです。結局、Java言語ではprintとprintlnメソッドは、Systemクラスのoutオブジェクトのメンバーなのです。)
JRubyでは(Smalltalkのように)、他の言語ではステートメント(またはキーワード)であるような機能を、数多くのメソッドが提供しています。一例として、Javaパッケージをプログラムにインポートするために使われるinclude_packageメソッドがあります。include_packageはクラスModuleのメンバーなので、モジュール定義の外で呼び出すとエラーとなります。ですからJRubyのアプリケーションでは、次のようなコードは珍しくありません。
module Swing include_package 'javax.swing' end |
JRubyでは(数字やストリング・リテラルを含めて)何でもオブジェクトなので、次のような表現を書くことができます。
3.type 5.0.to_s # to_s is like Java's toString() "abcde".length [1, 2, 3].size |
こうした表現は、Java開発者にとっては普通でないように見えるでしょうが、Smalltalkの開発者にとってはそんなことはありません。例えばJavaでMathクラスは、様々な数式機能を実行するパブリックな静的メンバーを幾つか定義します。JRubyではこれが必要ありません。Javaとは異なり、Math.round(3.9)は呼ばず、Smalltalkを知っている人にはお馴染みの3.9.roundを使うのです。
このセクションでは、変数の名前に関するJRubyのルールを見て行きます。このルールはBasicを思い出させます。(例えばJavaではクラス名の頭文字は大文字にするというような)名前の付け方のルールと、構文のルールとは違うことに注意してください。変数の名前の付け方に関するJRubyでのルールは、後者の範疇になります。つまり変数の名前は、JRubyプログラム内でのその変数の使い方や役割に直接関係しています。ですから表1で概略を示すような名前付けルールに正しく従わないと、エラーが起きます。
表1. JRubyのルール:変数の名前
| プレフィックス | 想定される役割 | 例 |
|---|---|---|
| $ | グローバル変数 | $total, $count, $_ |
| : | シンボル(つまり、変数名自体) | :foo, :size |
| @ | クラス・インスタンスのメンバー | @size, @foo |
| @@ | クラス・メンバー変数(Javaの静的メンバーのようなもの) | @@refCount, @@NORTH |
| 大文字 | 定数(全てのクラス名、モジュール名で使う必要がある) | MyClass, MyModule |
| 小文字またはアンダースコアー | 状況により、ローカル変数名またはメソッド名 | k, _id, name, getName |
後で出てくる例を見れば、こうしたルールが具体的にどのように動作するかが分かるでしょう。
皆さんはリスト2に示すプログラム出力の中に、timesやdownto、stepなど、あまり馴染みのないメソッド名があることに気がついたかも知れません。こうしたメソッドはJRubyではイテレーターとして知られています。イテレーターというのは、一連の値で指定のコード・ブロックを呼び出すメソッドです。この一連の値はそのイテレーターの実装と、イテレーターの定義の対象となっているオブジェクトのタイプで決まります。(ここでイテレーターというのは、クラスのメンバー・メソッドのようなものであり、それ自体はC++やJavaでのようなクラスではないことに注意してください。)
簡単な例としてイテレーターeachを考えてみてください。これはどのJRuby集合に対しても実装されています。eachイテレーターは単純に、集合の各メンバーにある指定のコード・ブロックを呼び出します。ですからリスト1にある例を下記のように書き直して、Integerクラス特有のコードを取り込んでしまうことができます。
3.methods.each { |m| print "#{m} " if not methods.index(m) } |
このコードは明らかに、for構造体を使ったリスト1よりも簡潔です。さらに、この手法は同等なJavaコードよりもずっと面倒さが少ないものです。コードが単純であることによって、細かくリストを追ったり、そのリストの各メンバーで操作を呼び出したりすることよりも、本来の仕事に集中することができるようになります。
リスト2を見るとIntegerクラスのメソッドに続いて、整数値自体から始まってファンクションの引き数で定義される値で終わる連続的な整数値を、uptoイテレーターがコード・ブロックに渡しているのが分かります。従って次のような行を書けば、1から5までの数字の2乗値の表を得ることができます。
1.upto(5) { |n| print "#{n}\t{n**2}\n" } |
stepイテレーターは、stepパラメーターを取ることを除けばupto(またはdownto)と似た動作をします。stepパラメーターは、次の値を得るために現在の値に加える(あるいは減じる)数値を表します。こうしたメソッドを使ってみると、なぜfor構造体の必要性が無くなるかが容易に分かります。(実際JRubyのforステートメントは、Integerクラスのeachイテレーターで実装されます)。
クラスで使えるイテレーターは、そのクラスの目的や操作の定義によって変わります。JRubyではyieldステートメントを使うことで、ユーザー独自のクラスのための独自のイテレーターを定義(またはJRubyのクラス・ライブラリーでイテレーターを書き直し)することができます。yieldステートメントは、制御をファンクション(またはメソッド)から、そのファンクションに渡されるブロックに移します。例えばIntegerオブジェクトに対するuptoイテレーターの実装は、次のように書くことができます。
Class Integer
:
def upto (endNum)
for val in self.to_i..endNum
yield val
end
end
:
end
|
カレント・インスタンスを参照するためには特別な識別子selfが使われ、Javaでのthis参照と全く同じように機能します。またself参照もthis参照と同じように、存在しない時には暗黙的です。
JRubyでは、表現self.to_i..endNumは、Rangeクラスのインスタンスを強制的に生成します。Rangeオブジェクトにはfirst値とlast値、そしてfirstに続く連続的な値をどのように構成するかを定義するメソッドsuccがあります。for構造体はIntegerのsuccメソッドを利用することによって、selfが表現する値からendNumで与えられる値までの範囲を構成します。ここでは3つ連続したピリオド( ... )で表される排他的Rangeオブジェクト(exclusive Range object)ではなく、2つ連続したピリオド( .. )で表される包含的Rangeオブジェクト(inclusive Range object)を使っています。
uptoメソッドの定義には、実際には2つのパラメーターがあります。一つは endNumであり、これはメソッド定義から明らかです。ところがもう一つのパラメーター、blockは、このメソッドの暗黙的な正式パラメーターの中にあり、存在する場合には、yieldステートメントを呼び出すことで自動的にアクセスされます。こうしたことから分かるのは、JRuby開発者はループ構造体を使う必要が無い、ということです。充実したJRubyのライブラリーを利用するか、あるいは自分のクラスにある適当なメソッド(例えばsuccやeachなど)を上書きすることで、些末な作業の大部分をイテレーターにさせてしまうことができるのです。
さてこれでJRubyでのクラスの例に取りかかる準備が出来ました。ここでは2つのクラスを書きます。最初はJRubyの持つ強力なテキスト処理機能を生かすもの、次はJavaのクラス・ライブラリーと組み合わせた場合の可能性を探るものです。どちらのクラス例のソースも、ダウンロード用のコードの中にあります。記事の先頭または最後にある Codeアイコンをクリックしてコードにアクセスしてください。
JRubyの最初のクラス定義は次のような形式の、ごく基本的なXML要素の表現です。
<tag>'</tag> |
このクラスはごく単純に見えますが、リスト3で分かるように、非常に多くのことをします。
リスト3. XmlElement.rbのクラスの抜粋
# represent a well-formed XML element
# (that is, opening and closing tags are present) with proper nesting
class XmlElement
# class member
# matches strings of the form:
# <tag>'</tag>
@@xmlElement = /<([a-z]+)>((.|\s)*)<\/\1>/
# member attributes: make them readable/writable
attr_accessor :name, :children, :textContent
def initialize (text, isRoot=true)
# member variable:
# list of child elements
@children = []
# member variables:
# name of element derived from tag name
# text content of element (if any)
@name, @textContent = "DOC_ROOT", "" if isRoot
# add new elements while we get a match
text.scan(@@xmlElement) { | name, content |
newChild = XmlElement.new(content, false)
newChild.name = name
newChild.textContent = content if not content.include?('<')
@children.push(newChild)
}
end # initialize
def printTree (elem=self, indent="")
print indent, elem.name
print "[#{elem.textContent}]" if elem.textContent
print "\n"
indent += " "
elem.children.each { | elem | printTree(elem, indent) }
end # printTree
end # XmlElement
|
このコードでは非常に多くのことが行われています。最も重要な要素を一行一行見て行きましょう。クラス定義はclassキーワードで始まり、次にクラス名が続きます。JRubyでのクラス名は定数であり、これは名前の先頭を大文字にすることで示されます。次の行は正規表現を宣言しており、この基本的なXML要素に対するテンプレートとなります。プレフィックス「@@」が付いていることで分かるように、この表現はXmlElementクラス・メンバーであることに注意してください。そしてこのクラス・メンバーの型はRegexpです。この表現を作るにはRegexpクラスのnew演算子を使うこともできますが、ここではPerl開発者にお馴染みの、より伝統的な構文を使っています。
次に、このクラスのインスタンス・メンバーを表す3つのシンボル、name、children、textContentを導入します。それぞれにはプレフィックスとしてコロン(:)を付けてSymbol型のシンボルとします。これはModuleクラスのattr_accessorメソッドでは必要です。このメソッドを使うのは、このメソッドを読んだり、メソッドに割り当てたりするための属性名が必要なためだけなのですが、あたかも対応するゲッターとセッターがあるかのようにメンバーに振る舞わせるのと同じことになります。クラス・メソッド本体の中で、属性にはプレフィックス「@」を付け、インスタンス・メンバーであることを示します。
initializeメソッドはインスタンス化されると、どのJRubyクラスの中でも最初に呼ばれるメソッドになります。ここでこのメソッドには、構文解析すべきXML要素のテキストが渡されています。@childrenリスト(child要素を保持するリスト)を初期化した後、こうした要素それぞれに対する再帰的な定義を開始します。(ここではXML文書の先頭の象徴として、デフォルトのroot要素を作っていることに注意してください。isRootのデフォルト値は、よりインターフェースが便利なように、trueに設定されます。)
ここではStringクラスのscanイテレーターを使って大部分の構文解析作業をしています。scanファンクションの最も基本的な機能は、正規表現の形をとり、(その表現に一致して)scanを呼び出すストリング・オブジェクト内にあるストリング(つまり、テキスト)の配列を返す、というものです。別の使い方として(私達の使い方では)、scanは一致したものの中にあるコード・ブロックを呼び出します。scanの面白いところは、表現の中のサブマッチ(submatches)を処理する点です。例えばここの例では、@@xmlElementは2つの副次式(subexpressions)、([a-z]+)と((.|\s)*)を含んでいます。これらはそれぞれ、要素の名前と、要素内容の残り部分を表します。そうするとscanメソッドが返す各配列要素はそれ自体、これら2つの結果を含む、2つのメンバーから成る配列になります。ですから、ここでのブロックは2つの引き数、nameとcontentをとります。
このブロック自体は単純です。新しいXmlElementを作り、一致したものの中にある内容をこれに対して渡します。名前と、新たに作ったXmlElementのテキスト内容を設定し、Stringクラスのinclude?メソッドを使って、このブロックの内容がテキスト内容として使えるかを調べます(残念ながら、これはあまり洗練されたテストとは言えません)。そして最後に、新たに作ったchild要素を、この要素に対する子のリストにプッシュします。
ダウンロードしたソースコードの中にあるXmlElement.rbファイルで、JRubyが長い(複数行の)ストリングの入力をサポートしていることが分かります。インタープリターに対して次のように直接タイプすると、XML文書のパーサーを呼び出します。
load "XmlElement.rb" # access the XmlElement class
text = IO.readlines("test.xml").join
xmlDoc = XmlElement.new(text)
xmlDoc.printTree
|
printTreeメソッドはその文書の中で見つかった要素を、(もしテキスト内容がある場合には)そのテキスト内容と共に出力します。子要素は読みやすいように、字下げされます。
これから紹介する2番目のクラス例ではGUIによる電卓を作るので、ずっと複雑になります。この例では2つの新しいJRubyクラス、HashとProcを導入し、どのようにしてJRubyアプリケーションの中でJavaクラスにアクセスし、使用するかを説明します。またこの例を見れば、JRubyを使うことで、複雑なアプリケーションがいかに素早く簡単に書けるかが分かるでしょう。
コード・ソースから電卓を実行するには、次のようにタイプします。
jruby Calculator.rb |
前の例と同様、Moduleクラスからのinclude_packageメソッドを使ってJavaパッケージをJRubyプログラムにインポートします。このメソッドは、他のクラスやファンクションと共にJRubyのJavaクラス・ライブラリーで提供されています。ですから、Javaクラスを利用する典型的なJRubyアプリケーションは次のように始まります。
require 'java' module <name> include_package '<name_of_Java_package>' end |
ここで注意して欲しいのですが、Javaライブラリーを含めるために、またJavaパッケージが置かれる名前空間を表現するモジュールの定義に、requireステートメントを使っています。
この電卓インターフェースは3つのコンポーネントから成り立ちます。
- 評価すべき数式を入力するためのテキスト・フィールド
- 0から9までの数字、基本的な演算操作、小数点、イコール記号(=)を含む、4 x 4格子のボタン
- sin、cos、sqrt(平方根)など、一般的な演算を行うためのボタン類を含んだファンクション部分
大仕事のように見えませんか? ところがJRubyのおかげで、この作業が信じられないほど単純で簡単なものになってしまうのです。
ここではJavaプラットフォームのSwingクラスを使ってGUIを作ることにします。この電卓自体はJFrameのサブクラスとして実装されます。ですから、リスト4にあるようなコードから始めることになります。
リスト4. 電卓を設定する
require 'java'
module Swing
include_package 'java.awt'
include_package 'javax.swing'
end
module AwtEvent
include_package 'java.awt.event'
end
$calculator = Swing::JFrame.new
class << $calculator
def init
end
end
$calculator.init
$calculator.setSize(400, 400)
$calculator.setVisible(true)
|
requireメソッドは、JRubyのJavaライブラリーをインタープリターの中にロードし、またJavaクラスを利用するどのアプリケーションでも必要なものです。次に、それぞれ一連のJavaパッケージに対する名前空間を表す、2つのモジュールを定義します。クラス名と同様、モジュール名は定数である必要があるので、先頭は大文字になります。Moduleクラスのinclude_packageメソッドを使ってJavaパッケージをモジュールの中にインポートします。このパッケージの中のクラスにアクセスするためには、モジュール名の後に名前空間解決演算子( :: )を付け、その後に必要なJavaクラス名を付けて、モジュール名を参照するようにします。
次のちょっとしたコードは、Javaプログラマーにとっては普通でないように見えるかも知れません。最初に、メインの電卓のウィンドウに対するグローバル変数を、識別子としてドル記号($)のプレフィックスを付けて定義します。このクラス定義は、JRubyでは無名クラス(anonymous class)として知られています。基本的にここでは、私達が今ちょうど作ったオブジェクトに基づく、あるいは今ちょうど作ったオブジェクトから継承するクラスを作りたいわけです(JRubyでの継承の実装の制限から、Javaクラスから直接継承するクラスは定義できません)。新しいサブクラスを定義するために、(initializeメソッドは$calculatorオブジェクトを作る時に既に呼ばれているので)initメソッドを書きます。クラス定義が完了したら、このメソッドを呼ぶ必要があります。(ある意味では、Javaクラス・コンストラクターでsuper()を呼び、次にそのクラスの初期化に進むのに似ています。)
このコードはごく素直なものです。ここでは単純に、評価すべき数式をユーザーが入力するためのラベルとフィールドが必要なだけです。リスト5は新しいinitメソッドを示します。
リスト5. 数式フィールドを初期化する
def init
cp = getContentPane
# field for entering expression
exprPanel = Swing::JPanel.new(Swing::BorderLayout.new)
exprBox = Swing::Box.new(
Swing::BoxLayout::X_AXIS)
exprBox.add(Swing::Box::createHorizontalGlue)
exprBox.add(
Swing::JLabel.new("Enter an expression: "))
@exprField = Swing::JTextField.new(20)
exprBox.add(Swing::Box::createHorizontalStrut(4))
exprBox.add(@exprField)
exprBox.add(Swing::Box::createHorizontalGlue)
exprPanel.add(Swing::BorderLayout::NORTH, exprBox)
cp.add(Swing::BorderLayout::NORTH, exprPanel)
end
|
ボタンのリスナーはボタンの内容にアクセスする必要があるので、テキスト・フィールドをインスタンス・メンバーにします。下記のようなメソッドを追加して、適切なアクセスができるようにします。
def setExpression (expr)
@exprField.setText(expr)
end
def getExpression
@exprField.getText
end
|
ここまで来ると、なぜJRubyが素晴らしいかが本当に分かってきます。次に必要なのは、伝統的な数字ボタンを作るために使う4 x 4格子のボタンです。ご存じのように、典型的な電卓には0から9までの数字ボタン、4つの演算(加減乗除)用のボタン、それにイコール記号のボタンがあります。単純にするために、押されると(イコール記号のボタンを除いて)どのボタンも、そのボタンが表す記号を右詰めで数式フィールドに表示します。イコール記号のボタンは実際の計算結果を表します。
これに関係のあるinitメソッドのコードを、リスト6に示します。
リスト6. 数字ボタンを初期化する
# form and add the number pad
buttons = [
'7', '8', '9', '/',
'4', '5', '6', '*',
'1', '2', '3', '-',
'0', '.', '=', '+'
]
numPadPanel = Swing::JPanel.new(
Swing::GridLayout.new(4, 4))
buttons.each { |symbol|
numPadButton = Swing::JButton.new(symbol)
numPadButton.setActionCommand(symbol)
numPadButton.addActionListener(<to_be_written>)
numPadPanel.add(numPadButton)
}
cp.add(numPadPanel, Swing::BorderLayout::CENTER)
|
ボタンを作りたい記号に対して繰り返し操作を行います。電卓の数字ボタン上に表記される各記号に対して最初に、処理対象の記号から成るテキストが付いた新しいJButtonを作り、次にボタン・リスナーを追加します(ただしこれはまだ書かれていません)。最後に、新しく作ったボタンをnumPadPanelに追加します。この繰り返しが終わったら、numPadPanelをメインのウィンドウに付加します。同じことをJavaですることを考えてみれば、なぜ多くの開発者がJRubyを好むのかが分かるでしょう!
数字ボタンに対するボタン・リスナーは、イコール記号を除いて、ボタンに記された記号で数式フィールドを更新する必要があります。イコール記号ボタンが押されたらば、数式を計算します。そのようなリスナーを、リスト7に示すようにグローバル変数として定義します。
リスト7. リスナーを定義する
$numListener = AwtEvent::ActionListener.new
def $numListener.actionPerformed (event)
comm = event.getActionCommand
$calculator.setExpression(
$calculator.getExpression + comm) if
comm != "="
$calculator.setExpression(
eval($calculator.getExpression).to_s) if
comm == "="
end
end
|
ここでは、$calculatorオブジェクトをJFrameのインスタンスにした時と同じ手法を使いますが、構文のみが異なります。Java開発者であれば、この構文がJavaでの無名内部クラスの定義に使われる構文と鏡像のようなものだと気がつくでしょう。
最後の課題は、ファンクション・ボタンを収納するパネルを実装することです。ここでは、ボタン上に表記されているものと同じファンクション名をキーとしたJRubyのHash(これはJavaのjava.util.HashMapのように振る舞います)を使います。Hashオブジェクトの値はProcオブジェクト(クロージャーと似ています)として知られており、Kernelクラスのprocメソッドで作られます。initメソッドの最後の部分は次のようになります。
リスト8. ファンクション・パネルを初期化する
# form and add the function panel
@fnMap = {
'sin' => proc { |n| Lang::Math.sin(n) },
'cos' => proc { |n| Lang::Math.cos(n) },
'ln' => proc { |n| Lang::Math.log(n) },
'sqrt' => proc { |n| Lang::Math.sqrt(n) }
}
fnPanel = Swing::JPanel.new(
Swing::GridLayout.new(1, 4))
@fnMap.each_key { | fnName |
fnButton = Swing::JButton.new(fnName)
fnButton.setActionCommand(fnName)
# we'll write the $fnListener momentarily --
# it's quite similar to the $numPadListener just discussed
fnButton.addActionListener($fnListener)
fnPanel.add(fnButton)
}
cp.add(fnPanel, Swing::BorderLayout::SOUTH)
|
作られた各Procオブジェクトはコード・ブロックをカプセル化することに注意してください(このコード・ブロックにあるのは、数学的機能の呼び出しと、このブロックに与えられた引き数のみです)。引き数は、Procオブジェクトのコール・メソッドを呼び出す時に渡されます。
ここではfnMapをインスタンス・メンバーにします。これも、ファンクション・ボタンに必要なリスナーで、fnMapに対するアクセサー(accessor)を提供できるようにするためです。クラスに次のようなメソッドを追加することで、適切なアクセスができるようにします。
def getFnForName (name)
@fnMap[name]
end
|
このリスナーはグローバルに定義され、また、数字ボタンに対するボタン・リスナーを作るために使ったものと同じメソッドを使って作られます。
$fnListener = AwtEvent::ActionListener.new
def $fnListener.actionPerformed (event)
val = $calculator.getExpression.to_f
fn = $calculator.getFnForName(event.getActionCommand)
$calculator.setExpression(fn.call(val).to_s)
end
|
数式フィールドから値を取り出すと(数式が一つの値になったと仮定します)、必要なProcオブジェクト取得してcallメソッドを呼び出し、数式フィールドから取り出した値を渡すことができます。次にそれをto_sメソッドで変換してストリングに戻し、数式フィールドをリセットします。
図1はこのアプリケーションで作ったGUIです(美しくはありませんが、実際に動作するのです!)
図1. 電卓のGUIの例
ここでお見せした(参考文献にもあります)電卓の例は、完全に動作することに加えて、拡張も簡単にできます。電卓機能を拡張するには、
$fnMap
の新しい機能それぞれに対して、適当なエントリーを単純に追加します。
JRubyは強力で柔軟な言語であり、豊富なライブラリーと幅広い機能を持っています。イテレーターをサポートしていることや便利なテキスト処理機能、それにもちろん、膨大なJavaクラス・ライブラリーやAPIにアクセスできることなどから、大小様々な、堅牢なアプリケーションを容易に素早く書くための環境として理想的なものです。
alt.lang.jreシリーズの今回の記事では、JRubyを紹介し、簡単でお馴染みの例を使いながら、高速開発向きの言語としての使い方を見てきました。この記事で使ったソースコードへのリンクは参考文献にあります。先に説明した通り、さらに練習しようと思えば、クラス例の幾つかは容易に拡張することができます。
参考文献を見て、JRubyやその他のJavaプラットフォーム用代替言語についてさらに学んでください。そして来月の記事も必ず読んでください!
| ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|
| zip | HTTP |
- この記事の先頭または最後にある Codeアイコンをクリックして、この記事で使用したサンプル・コードをダウンロードしてください。
- SourceForgeにあるJRubyのホームページからJRubyをダウンロードしてください。
- SourceForgeはJRuby projects pageもホストしています。ここには進行中の最新ファイル・リリースがあります。
- 何人かの代替言語エキスパートの協力による、alt.lang.jreシリーズの他の記事も読んでください。
- Jrubyはプログラミング言語Rubyの純Java実装です。Rubyについてさらに詳しくはRubyCentralで学んでください。
-
The Functional Approach to Programming(Guy Cousineau and Michel Mauny, 1998年Cambridge University Press刊)で機能プログラミングについてさらに学んでください。
- developerWorksのJava technologyゾーンにはJavaプログラミングのあらゆる面に関する記事が豊富に取り揃えられています。
-
Developer BookstoreにはJava関係の書籍を含め、広範な話題の技術書が豊富に取り揃えられていますので、ご覧ください。

