alt.lang.jre: JRubyを好きになってください

Javaツールボックスにちょっとした輝きを

JRubyはSmalltalkの持つオブジェクト指向の強みとPerlの表現力、Javaクラス・ライブラリーの柔軟性を組み合わせ、効率的、高速開発を実現するフレームワークを作り上げます。alt.lang.jreシリーズ3回目の今回の記事では、Michael SquillaceとBarry Feigenbaumの2人が、Java開発ツールボックスに加えておくべき価値のある洗練されたツール、JRubyを紹介します。

Michael Squillace (masquill@us.ibm.com), Software Engineer, IBM

Michael SquillaceMichael Squillace博士はIBM Worldwide Accessibility Centerのメンバーであり、IBMのソフトウェア製品を障害者対応にするためのツール開発に従事しています。哲学で博士号を取得しており、現在はテキサス大学にてコンピューター・サイエンスを専攻する学生です。プロのピアニストでもありますが、盲目でもあります。



Barry Feigenbaum, Ph.D., Sr. Consulting IT Architect, IBM

Barry FeigenbaumBarry Feigenbaum博士はIBM Worldwide Accessibility Centerのメンバーです。ここでは障害を持った人たちにIBM製品を使いやすいものにするための努力が続けられています。Feigenbaum博士はJavaOne 2004で展示されたBlueSpaceウォールディスプレイ・プロジェクトの開発チームのリーダーです。技術書や記事を執筆しており、いくつかの特許も持っています。またJavaOneのような業界での会議でも発言しています。テキサス州オースチンにあるテキサス大学にて、コンピューター・サイエンスの非常勤助教授を務めています。



2004年 9月 08日

JRubyはJavaプラットフォーム用の代替言語で、Yukihiro Matsumoto氏(通称「Matz」)が開発したプログラミング言語であるRubyに基づいています。RubyCentralのホームページ(参考文献)にあるように、Rubyは「古典的オブジェクト指向言語Smalltalkの持つ純オブジェクト指向の力と、Perlのようなスクリプト言語の持つ表現力と便利さを組み合わせた」プログラミング言語です。すっきりとして直感的な構文や意味体系、透明で開発者に使いやすいプログラミング・モデルはよく知られており、また比較的成熟した言語です。

JRubyはRubyインタープリターの純Java実装です。このシリーズで取り上げる大部分の言語と同様、JRubyは強力であり、しかも簡単に学ぶことができます。JRubyはPerlの持つ高度なテキスト処理や、Groovy開発者にはおなじみのイテレーターやクロージャー、またこのシリーズで取り上げたJythonなどの言語の持つ、高速開発機能を取り入れています。JRubyもインタープリター型言語であり、従ってコマンドラインからの実行や、単純表現やコード・ブロックの評価をその場で行うことができます。

JRubyはあらゆる人のあらゆる用途に使えるものではありませんが、それまでの言語で持っているような重要な機能を確実に利用しており、強力でありながら構文的に単純な言語となっています。JRubyはこのシリーズで取り上げた大部分の言語とは異なり、一つのパッケージの中に強力な機能の大部分を取り込んでいます。例えば、JRubyはイテレーターとテキスト処理機能を組み合わせているため、強力かつ直感的なパーサーが比較的簡単に書けるようになっています。また、ファンクションがファーストクラス・データ型として使えるようになっています。そのためこれをブロックやクロージャーと組み合わせることによって、普通はループ構造体やもっと伝統的なイテレーター・クラスに任されるような、些末な仕事の大部分を処理するようにクラスを拡張することができます。

このシリーズについて

Java technologyゾーンの読者であれば、Java言語やクロス・プラットフォームの仮想マシンでJava言語がどのように実行するかをよく知っている人が多いと思いますが、Javaランタイム環境がJava言語以外の言語にも対応できることを知っている人は少ないかも知れません。このシリーズの記事では、JREに関する代替言語として数多くの言語を調査して行きます。このシリーズで皆さんが学ぶ言語の大部分はオープンソースであり、無料で使うこともできますが、中には購入する必要のある商用製品もあります。alt.lang.jreシリーズで取り上げる言語は全てJREがサポートするものであり、また動的で柔軟という、Javaプラットフォームの地位を高めるものと著者が信じているものです。

従って自然なこととして、JRubyはJavaプラットフォームとも緊密に統合されており、Javaクラス・ライブラリーの便利さと柔軟性を取り入れています。JRubyはモジュールやクラスをサポートしているので、スケーラブルで堅牢なアプリケーション開発に適しています。JRubyはJavaクラスをスクリプト記述するために使ったり、(インタープリターを通して)直接Javaアプリケーションに埋め込んだりすることができます。RubyもJRubyも共にオープンソースの言語であり、無料で開発や展開に使用することができます。

alt.lang.jreシリーズ3回目の今回の記事では、JRubyを集中的にとり上げます。最初は基本的なインストールの仕方を説明し、その後で、なぜJRubyがJava開発ツールボックスに加える価値のあるものなのかを説明します。残りはコード中心の説明になります。簡単な例から始め、学習しながら次第に複雑な例に移って行きます。ソースコードは、このページの一番上または一番下にあるCodeボタンをクリックしてダウンロードしてください。

どこでJRubyを入手するか

この記事の執筆時点では、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はファンクションをファーストクラスのデータ型としてサポートしています。つまりファンクションは変数に割り当てることができ、コレクションの中に置くことができ、他のファンクションとの間でやり取りすることができます。またJRubyはインタープリター型で動的型付け言語(dynamically-typed language)なので、動的な処理(on-the-fly processing)に最適です。JRubyの便利な機能としては、次のようなものが挙げられます。

  • 簡単な構文:JRubyには(Javaのセミコロンのような)ステートメント終了子がなく、引き数を持たないファンクション・コールでは、括弧を省くことができます。
  • 変数には型がなく、従って宣言の必要がありません。
  • クラスのメンバー属性は読み取り可能として、かつ/または書き込み可能として規定でき、名前でアクセスすることができます(つまり、ゲッターやセッターを使う必要がありません)。
  • ブロックやイテレーターがあるために、特に集合のメンバーに対する繰り返し評価が容易になります。
  • ストリング表現や正規表現機能が充実しているため、テキスト処理が非常に単純です。

このような機能(そして他にも機能があります)がJavaプラットフォームの力と組み合わされることによって、JRubyはJava開発者にとって素晴らしい代替言語となります。


Hello World!

これから先では、コード例を学び、それを解説して行くことにします。最初の例として、典型的な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] == 1x[1] == 1x[2] == 0のようなものになります。このプログラムはまた、+- などの典型的な数式表現、それに>< のような条件演算子も返します(downtostepなど、このリストにある他のメソッドについては、すぐ後で触れることにします)。


純オブジェクト化

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言語ではprintprintlnメソッドは、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のルール

このセクションでは、変数の名前に関するJRubyのルールを見て行きます。このルールはBasicを思い出させます。(例えばJavaではクラス名の頭文字は大文字にするというような)名前の付け方のルールと、構文のルールとは違うことに注意してください。変数の名前の付け方に関するJRubyでのルールは、後者の範疇になります。つまり変数の名前は、JRubyプログラム内でのその変数の使い方や役割に直接関係しています。ですから表1で概略を示すような名前付けルールに正しく従わないと、エラーが起きます。

表1. JRubyのルール:変数の名前
プレフィックス想定される役割
$グローバル変数$total, $count, $_
:シンボル(つまり、変数名自体):foo, :size
@クラス・インスタンスのメンバー@size, @foo
@@クラス・メンバー変数(Javaの静的メンバーのようなもの)@@refCount, @@NORTH
大文字定数(全てのクラス名、モジュール名で使う必要がある)MyClass, MyModule
小文字またはアンダースコアー状況により、ローカル変数名またはメソッド名k, _id, name, getName

後で出てくる例を見れば、こうしたルールが具体的にどのように動作するかが分かるでしょう。


ブロックとイテレーター

皆さんはリスト2に示すプログラム出力の中に、timesdowntostepなど、あまり馴染みのないメソッド名があることに気がついたかも知れません。こうしたメソッドはJRubyではイテレーターとして知られています。イテレーターというのは、一連の値で指定のコード・ブロックを呼び出すメソッドです。この一連の値はそのイテレーターの実装と、イテレーターの定義の対象となっているオブジェクトのタイプで決まります。(ここでイテレーターというのは、クラスのメンバー・メソッドのようなものであり、それ自体はC++やJavaでのようなクラスではないことに注意してください。)

簡単な例としてイテレーターeachを考えてみてください。これはどのJRuby集合に対しても実装されています。eachイテレーターは単純に、集合の各メンバーにある指定のコード・ブロックを呼び出します。ですからリスト1にある例を下記のように書き直して、Integerクラス特有のコードを取り込んでしまうことができます。

3.methods.each { |m| print "#{m} " if not methods.index(m) }

このコードは明らかに、for構造体を使ったリスト1よりも簡潔です。さらに、この手法は同等なJavaコードよりもずっと面倒さが少ないものです。コードが単純であることによって、細かくリストを追ったり、そのリストの各メンバーで操作を呼び出したりすることよりも、本来の仕事に集中することができるようになります。

もはやforループが不要・・・

リスト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のライブラリーを利用するか、あるいは自分のクラスにある適当なメソッド(例えばsucceachなど)を上書きすることで、些末な作業の大部分をイテレーターにさせてしまうことができるのです。


JRubyのクラス動作

さてこれで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つのシンボル、namechildrentextContentを導入します。それぞれにはプレフィックスとしてコロン(:)を付けて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つの引き数、namecontentをとります。

このブロック自体は単純です。新しいXmlElementを作り、一致したものの中にある内容をこれに対して渡します。名前と、新たに作ったXmlElementのテキスト内容を設定し、Stringクラスのinclude?メソッドを使って、このブロックの内容がテキスト内容として使えるかを調べます(残念ながら、これはあまり洗練されたテストとは言えません)。そして最後に、新たに作ったchild要素を、この要素に対する子のリストにプッシュします。

XML文書処理

ダウンロードしたソースコードの中にあるXmlElement.rbファイルで、JRubyが長い(複数行の)ストリングの入力をサポートしていることが分かります。インタープリターに対して次のように直接タイプすると、XML文書のパーサーを呼び出します。

load "XmlElement.rb"   # access the XmlElement class
text = IO.readlines("test.xml").join
xmlDoc = XmlElement.new(text)
xmlDoc.printTree

printTreeメソッドはその文書の中で見つかった要素を、(もしテキスト内容がある場合には)そのテキスト内容と共に出力します。子要素は読みやすいように、字下げされます。


GUIを作る

これから紹介する2番目のクラス例ではGUIによる電卓を作るので、ずっと複雑になります。この例では2つの新しいJRubyクラス、HashProcを導入し、どのようにしてJRubyアプリケーションの中でJavaクラスにアクセスし、使用するかを説明します。またこの例を見れば、JRubyを使うことで、複雑なアプリケーションがいかに素早く簡単に書けるかが分かるでしょう。

コード・ソースから電卓を実行するには、次のようにタイプします。

jruby Calculator.rb

Javaクラスにアクセスする

前の例と同様、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の例
電卓のGUIの例

ここでお見せした(参考文献にもあります)電卓の例は、完全に動作することに加えて、拡張も簡単にできます。電卓機能を拡張するには、$fnMapの新しい機能それぞれに対して、適当なエントリーを単純に追加します。


まとめ

JRubyは強力で柔軟な言語であり、豊富なライブラリーと幅広い機能を持っています。イテレーターをサポートしていることや便利なテキスト処理機能、それにもちろん、膨大なJavaクラス・ライブラリーやAPIにアクセスできることなどから、大小様々な、堅牢なアプリケーションを容易に素早く書くための環境として理想的なものです。

alt.lang.jreシリーズの今回の記事では、JRubyを紹介し、簡単でお馴染みの例を使いながら、高速開発向きの言語としての使い方を見てきました。この記事で使ったソースコードへのリンクは参考文献にあります。先に説明した通り、さらに練習しようと思えば、クラス例の幾つかは容易に拡張することができます。

参考文献を見て、JRubyやその他のJavaプラットフォーム用代替言語についてさらに学んでください。そして来月の記事も必ず読んでください!


ダウンロード

内容ファイル名サイズ
zip---

参考文献

  • この記事の先頭または最後にある 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関係の書籍を含め、広範な話題の技術書が豊富に取り揃えられていますので、ご覧ください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218776
ArticleTitle=alt.lang.jre: JRubyを好きになってください
publish-date=09082004