| |||
Java 言語は,開発者の視点からみるとコンパイル言語であると言えます. つまり,エディタなどでソースコードを編集した後, 「コンパイル」という作業を行いソースコードをバイトコードに変換してはじめて, 実行することができます.コンパイルタイムに静的な型チェックなどを行えるため, ランタイムのバグを少なくすることができるという特徴がある半面, 「ちょっと書いて試して見る」とか, 「書いては直し,書いては直しを繰り返す」という作業をするには,開発効率が悪いと言えます. 逆に,スクリプト言語はコンパイル作業がない分, 上記のようなラピッド・プロトタイプ型の開発を非常にスムーズに行うことができます. また,一般にシンタクスが簡単で覚えやすく,宣言が少なく,提供される機能のレベルが高いため, 少ない行数でかなりのことができてしまう,という利点もあります. その分に型に関しては悪く言えば曖昧です. ソースコード中の単純な書き間違いなどの簡単なバグもランタイムでしか発見できないため, 大規模な開発には向いていません. スクリプト言語のもう1つの特徴は, プログラムをあらかじめ記述したファイルを用意しなくても, 1行ずつ手で入力して実行することにより, フィードバックを見ながら行き当たりばったりで実行してみることができる点です. このような使い方は,「シェル」と呼ばれます. このように,コンパイル言語とスクリプト言語は一長一短があるのですが, うまく組み合わせることによって,全体として非常に柔軟なシステムを作ることができます. ここでは,コンパイル言語としての Java との組み合わせにおいて, スクリプト言語である Pnuts, Jacl, JPython を比較してみようと思います. |
Java とスクリプト言語の組み合わせが威力を発揮する身近な例として, 次のようなものが考えられます. ●Java アプリケーションのテストケースを,スクリプト言語で記述する. Java で書かれたコードのテストケースを大量に用意する場合, スクリプト言語でそれを記述するとテストの効率が上がります. テストケースは一般に小さなプログラムであるため, それ自体の構造を設計する必要はほとんどの場合ありません. 手順を書き下すだけのことが多い一方,パターンは多く用意する必要があるため, スクリプト言語がこの用途にうってつけです. ●Java アプリケーションのカスタマイズをスクリプト言語で行う. これは,ちょうど Visual C++ のアプリケーションと,Visual Basic のような関係です. カスタマイズ用言語としてスクリプト言語を採用すれば, アプリケーションとのインターフェイスを,簡単に,高いレベルで記述できることができます. このような機能は,「マクロ」とも呼ばれ, エディタ・アプリケーションなどでは,広く行われているカスタマイズ方法です. ●スクリプト言語の動的な解釈実行エンジンと Java を組み合わせる 上記の2つの例が Java 主体であったのに対し,この例はスクリプト言語側に主体があります. スクリプト言語の強みは,その解釈実行エンジンにあります. 例えば文字列データを受け取って,それをプログラムとして実行したりすることが可能です. これについては後程,実例で紹介します. |
この記事で取り上げる Pnuts, Jacl, JPython は,100% pure Java で実装されているので, 言語の処理系自身も JavaVM 上で動いています. それでは,それぞれの特徴を簡単に説明します. Pnuts は Java 専用のスクリプティング言語として設計されています. そのため, Java とのバインディングは最もシンプルです. pnutool というシェル環境も用意されていて, 対話的に Java オブジェクトを操作するには一番便利で手軽だと思います. Jacl は Tcl の Java による実装です. 一時期 Tcl 作者 John Ousterhout 氏が Sun に在籍していた関係で, Sun は今年3月の JavaOne で Jacl を,JavaBeans用のスクリプト言語として発表しています. John Ousterhout 氏自身は,現在 Scriptics という会社を設立して独立しているようです. また,ここでは解説しませんが, Tcl Blend を使用すれば,既存の C のコードと Java を結合して動作させることも可能です. JPython は,オブジェクト指向スクリプト言語 Python を Java で実装したものです. シンタクスは通常の Python と全く同じですが,Java への対応として, JDK1.1 AWT のイベントモデル,JavaBeans プロパティを手軽に扱える機能などをフィーチャしています. また,この中では唯一のオブジェクト指向言語であり, Java で定義されたクラスをスクリプト上で継承してメソッドをオーバーロードしたり, JPython のコードを Java のバイトコードにコンパイルしたりもできます. こういった機能を利用すると,JPython で Applet を作ったりすることも可能になります. |
さて,前置きが長くなりましたが,まず各言語の雰囲気を見てもらうために, Hello World から始めましょう. Java との連携として,java.awt をスクリプトから使用してみます. このプログラムでは,java.awt.Frame と java.awt.Button を使用し, ボタンが押されたら,コンソールに "Hello !" という文字列を表示します. ボタンが押された時の処理(コールバック)のバインドのやり方に,各言語の特色が出てきます. |
hello.pnut
まず,Pnuts です.1行目から説明します. import ("java.awt") これは,Java の import 文とよく似ています. ただし,java.awt と指定することで,awt 以下の全クラスが使えるようになります. ちょうど java の import java.awt.*; と意味的には同じになります. f = Frame("Hello") b = Button("Hello Scripting World") この2行は,それぞれ Frame クラス,Button クラスのオブジェクトを作成し, それぞれへの参照を f, b という変数に代入しています. 文字列は,コンストラクタの引数です. function helloPressed(event) { println("Hello !") } Pnuts では,関数を定義できます. この関数は,ボタンが呼ばれた時の動作を指定するものです,println 関数を使って, "Hello !" という文字列をコンソールに出力します. bind(b, "actionPerformed", helloPressed) この行で,ボタンとそれが押された時の動作を結びつけています. Java では通常, ActionListener インターフェイスを実装するクラスを使うことで アクションイベントを処理しますが, Pnuts ではアクションイベントとコールバック関数をバインドするという 手軽な方法をとっています. "actionPerformed" が,イベントのタイプで, helloPressed は,そのイベントが起きたときに呼ばれる関数となります. f.add(b) f.pack() f.show() この3行は,Java でも同様の記述となるので分かりやすいと思います. ボタンをフレームに張り付け,フレームをボタンの大きさにまでパックし, 最後にフレーム全体を表示しています. |
hello.tcl
次は,Jacl です.Jacl は言語的には Tcl の拡張になっているため, ファイルの拡張子は .tcl が使われます. Tcl はシンタクスが少し変わっているため,最初すこしとまどうかもしれません. Tcl の文は全て,1つのコマンドとそれに続く引数の並びという形をとります. command arg1 arg2 ... argn という構文です.代入は,set コマンドを使います. set var value という構文で,変数 var に value が値としてセットされます. 変数の値の取り出すには,$var と書きます. さらに,[ ] の中に Tcl のコマンドを書くと, その部分が [ ] 内のコマンドの実行結果に置き換えられて解釈されます. UNIX のシェルなどのバッククオート(`)機能と同等です. さて,hello.tcl の1行目です. set f [java::new java.awt.Frame "Hello"] この行で,フレームを生成しています. まず,[ ] の内部で,java::new コマンドを実行しています. 第一引数が java.awt.Frame で,第2引数が "Hello" という文字列です. このコマンドの実行結果は,Frame クラスのオブジェクトです. それを値として,f という変数にセットしています. set b [java::new java.awt.Button "Hello Scripting World"] 同様に,Button クラスのオブジェクトを生成しています. proc helloPressed { } { puts "Hello !" } これは,proc コマンドであり,新しいプロシージャを定義します. プロシージャは,Tcl でコマンドとして使用できます. このプロシージャでは,コンソールに "Hello !" という文字列を, puts コマンドを使って出力しています. java::bind $b actionPerformed helloPressed ここで,java::bind をコマンドを使って,イベントとコールバックを結びつけます. $f { add java.awt.Component } $b $f pack $f show この3行で,フレーム f に ボタン b を張り付け,フレームをパックして表示しています. ただし,最初の行では,Frame には,複数の add メソッドがあるため, シグニチャ(引数の型の並び)を指定して呼び出しています。 元祖 Tcl とその GUI である Tk では, GUI オブジェクトを生成するとその部品名が新たなコマンドとして登録されるという慣用があり,Jacl でもそれを引き継いでいます. すなわち,Tcl の構文上の解釈では,上記の $f が新たなコマンドになり, pack, show といった引数が $f コマンドに渡されます.ただ,実際はそう解釈しなくても, $f オブジェクトの pack, show メソッド呼び出し, とオブジェクト指向的に理解した方が分かりやすいでしょう. |
hello.py
最後に Python です. from java import awt from java.awt import Frame, Button import 文は,Java のそれとよく似ています.ただし, from java.awt import * という構文は,使えません.実は,Java 以外の Python モジュールには使えるのですが,java package モジュールには現在は使えないようです. Python では,Java のパッケージに当たるものをモジュールと呼びます. f = Frame('Hello') これは,'Hello' という文字列をコンストラクタに与えて, Frame クラスのオブジェクトを生成しています. Python では,文字列定数を指定するには, シングルクオート(')でもダブルクオート(")でもよいことになっています. 生成されたオブジェクトへの参照が,f という変数に代入されます. def helloPressed(e): print 'Hello !' これは関数の定義となっています. この関数は,後にボタンを押された時に呼び出されるものです. print 文でコンソールに 'Hello !' という文字列を出力します. b = Button('Hello Scripting World', actionPerformed=helloPressed) この文で,ボタンを生成し,先程定義した helloPressed 関数を押された時のコールバックとして登録します.actionPerformed=helloPressed というバインドの仕方が JPython 流で, Java のイベントモデルへのユニークな対応となっています. f.add(b) f.pack() f.show() この3行の意味は,説明不要でしょう. |
今度は,自分で作った Java のプログラムをテストする, という例を各スクリプト言語で記述してみます.テストするクラスは,以下のようなものです. Pin.java
この Pin クラスは,始点と終点をもつ幾何形状で, rotate という自分を回転するメソッドと, draw という自分を java.awt.Graphics に表示するメソッドを持っています. Graphics に表示される形状は,裁縫の"待ち針"のようなもので, 始点から終点に向かって線が引かれ,終点に黒丸が表示されます. たとえば,rotate メソッドは少し長めで, 1度コーディングしただけでは正常に動くかどうか自信がないところでしょう. このクラスのオブジェクトを各言語から生成,メソッド起動し, 実際に AWT を使ってうまく回転するかどうか確かめて見ます. |
pin.pnut
Pnuts での Pin のテスト例です. Java を知っていれば,簡単に読めると思います.注意点は,3行目の f = frame("Pin Test", 200, 200) で,Frame でなく,frame 関数を使用しているところです. Frame でもよいのですが,Pnuts では,ウインドウのクローズ処理に予め対応した Frame を返す,frame 関数を用意しています.よく使われるので,ここで使って見ました. f.show() g = f.getGraphics() f を表示し,getGraphics メソッドで f に対応する Graphics を取り出します. 後で,この Graphics に対して描画を行います. start = Point(100, 100) end = Point(100, 50) pin = Pin(start, end) java.awt.Point クラスのオブジェクトを2つ作ります. それぞれ Pin の始点と終点になります. その2点から,Pin クラスのオブジェクトを作ります. for (i = 0; i < 12; ++i) { pin.rotate(start, 30) println(pin) pin.draw(g) } 30度ずつ,12回 rotate を呼び出し,その都度,pin の内容をコンソールに表示し, Graphics に描画しています. println 関数内では,自動的に Pin クラスの toStringメソッドが呼ばれます. 注意点として,Pnuts では for の繰り返しの ++i を,i++ と書くことはできません. |
pin.tcl
Jacl での Pin のテスト例です. 個人的には,変数の値参照の時に $ をつけるため,全体的にかなり見にくく感じます.
set f [java::new java.awt.Frame "Pin Test"] $f setSize 200 200 $f show set g [$f getGraphics] フレームの生成,サイズの設定,表示,Graphics の取り出しです.
set start [java::new java.awt.Point 100 100] set end [java::new java.awt.Point 100 50] set pin [java::new pin.Pin $start $end] 始点と終点を指定して,Pin クラスのオブジェクトを生成しています. for {set i 0} {$i < 12} {incr i} { $pin rotate $start 30 puts [$pin toString] $pin draw $g } これが,Tcl の for コマンドです.Java や C と構造的にはよく似ています. やはり,12回,30度ずつ回して,puts でコンソールに内容を表示し,Graphicsに描いています. |
pin.py
最後に,JPython でのテスト例です. import sys sys.add_package('pin') まず,pin パッケージ(Pin クラスが存在するパッケージ)の存在を,system に登録します. レジストリを使って設定することも可能ですが,ここではプログラム中で登録しています. import pin from pin import Pin import java from java.awt import Point, Frame 必要なクラス名を,現在の名前空間にインポートします. f = Frame('Pin Test', size=(200,200)) フレームの作成です.サイズの指定に注目してください. JavaBeans のプロバティは,上記のように指定することができます.これは, f = Frame('Pin Test') f = setSize(200,200) と同じ意味になります. f.show() g = f.getGraphics() フレームを表示し,Graphics を取り出します. start = Point(100, 100) end = Point(100, 50) pin = Pin(start, end) 端点を指定して,Pin クラスのオブジェクトを生成します. for i in range(0, 12): pin.rotate(start, 30) print pin pin.draw(g) これが Python の繰り返しです.Python 言語の大きな特徴は, インデント(字下げ)を構文上のブロックとして積極的に利用している点です. : で終わる最初の行をヘッダ行と呼び, その下の同じレベルにインデントされた部分が1つのブロックとして解釈されます. ここでは,12回 30度ずつ pin を回し,print 文で内容をコンソールに表示し, 同時に Graphics に描いています. print 文の中では,自動的に Pin クラスの toString メソッドが呼ばれます. |
次は,もう少し面白い例です.Java アプリケーションがあるとして, そのマクロ言語としてスクリプトを使用してみます. アプリケーションといっても,ここでは以下のようなごく単純なフレームワークを考えて見ました. あらかじめマクロ言語を組み込めるように アプリケーションを設計するときのヒントになると思います. このフレームワークは,Application, Commmand, Document という3つの主要なクラスからできています. Application は,スクリプト言語からコマンドを受け取り, コマンドに execute メッセージを送ります. Command は execute メソッドを持った抽象クラスで, これを継承して実際の具体コマンドを作成していきます. ここでは,具体コマンドの例として,PrintCommand を実装しています. Document は,この例では空になっていますが, 実際にはアプリケーションの問題領域を記述したクラスです. Document は Command によって操作されます.
Application.java
Command.java
PrintCommand.java
Document.java
各スクリプトでは,Application を生成した後,PrintCommand を適当な引数で生成し, それを executeCommand メソッドを使って Application に引き渡しています. |
framework.pnut
まず Pnuts です.Application を生成し,PrintCommand を3回生成して,Application に実行を依頼します.コンソールには,
Hello 0, from Java Hello 1, from Java Hello 2, from Java と現れます. |
同様のことを,Jacl で行うと,以下のようになります. framework.tcl
出力は全く同じです。 |
次は,JPython です. framework.py
前半はほとんど説明が要らないと思います.for ブロックの中の, c = PrintCommand('Hello %d' % i) の箇所は,文字列,'Hello %d' の %d に i を入れた文字列を生成する % 演算子の機能を使用しています。 後半は,JPython のクラス定義機能を使って, JPython 内で新たに Command クラスを継承した,Print2Command クラスを作成してみました.そして,Print2Command クラスのオブジェクトを実際に生成して, Application に実行を依頼しています. class Print2Command(framework.Command): これが,クラス定義のヘッダ行です. Command を継承して,Print2Command を定義します. def __init__(self, message): self.message = message これがコンストラクタの定義です.__init__ はちょっと見にくい名前ですが, このような特殊な名前が,Python には幾つか予約されています. クラスの定義の中の関数定義(すなわちメソッド)では, 慣用的に最初の引数は,self という名前を持ち,これが Java でいう this に相当します. def execute(self, document): print self.message + ', from script' このメソッドで,message に ', from Script' という文字列を追加して, コンソールに出力しています.全体の出力は, Hello 0, from Java Hello 1, from Java Hello 2, from Java Bye, from Script となります.このように,JPython を使用すると Java アプリケーション側で定義されたクラスを拡張することができるため, スクリプト側でもコマンドをどんどん作成することが可能になります. |
最後に,Java とスクリプト言語を組み合わせた面白い例をお見せします. これは,JPython に付属のデモプログラムを,すこし短くしたものです. インタープリタの解釈実行エンジンをうまく利用しています. 入力された数式をグラフにして表示するものですが, こんなに短いプログラムで,こんなに高機能なことが実現できる,という例です. 例えば,Java だけでこれをやろうと思うと,数式のパーサを書くはめになります. さらに,パースした数式を数式オブジェクトに入れ,それを評価することになるでしょう. かなり骨が折れます.スクリプト言語は,もともとパーサを備えていますから, このようなことが簡単にできます. スクリプティングの威力がこれで分かると思います. また,JPython には通常のインタープリタの他に,jpythonc というスクリプトを Java のバイトコードにコンパイルする機能をもったコンパイラがついています. これを使えば,Java を全く書かずにアプレットを作ってしまうことも可能なのです. |
Graph.py
|
簡単なプログラムを使って,Java のオブジェクトをスクリプト言語から操作する例をいくつかお見せしました.スクリプト言語としては,Pnuts, Jacl, JPython を扱いましたが,他にも Ruby など,Java とのインターフェイスを持った言語がどんどん増えて来ています. Java とスクリプト言語の組み合わせの可能性は, これからますます大きくなっていくと期待しています. |
|
質問,感想は,平鍋まで.
Copyright (C) Kenji Hiranabe 1998, 1999 |