ObectSquare

[技術講座]


ガチンコPnuts道場

Thu Jul 26 10:08:53 JST 2001
菅野洋史
https://www.ogis-ri.co.jp/otc/hiroba/

目次


注意:この文章は、Pnutsスクリプトを最初に学ぶ人たちのために作成しました。ガチンコPnuts道場ということで最初はすごく攻撃的な文体でいこうと考えていましたが、想像以上にしっくりこない上に感じも悪かったので、今回は単に「だ、である」調で通すだけにしました。予めご了承ください。

はじめに

Pnutsって何

PnutsとはSun Microsystemsの戸松氏によって開発されたJavaVM上で動作するスクリプト言語である。Javaを用いて実装されているため、Javaが動く環境(JDK1.1以上)であればどこでも用いることができ、PnutsスクリプトからJavaのAPIのクラスを自由に操作して利用することがきる。またソースコードも公開されている。
Pnutsの主な特徴としては、以下の点が挙げられる。
またそれ以外の特徴として、「Pnuts自身はオブジェクト指向言語ではない」という点がある。クラスを定義したりするときはJavaで行い、Pnuts側からオブジェクトを(生成したりして)利用することになる。
Pnutsの主な利用法は以下の形になるだろう
Javaクラスの単体テスト
コンパイルの手間をかけずにコーディングができるうえ、スクリプト上もしくは対話型のシェルから気楽にJavaクラスのオブジェクトを生成したりメソッドを呼び出したりすることができる。したがって修正→テストの開発サイクルが非常に早い
Javaコンポーネントの糊言語として
いわゆるRAD的な開発ではクラスを一から設計するのではなく既存のコンポーネントを組み合わせることによって開発をおこないたい。PnutsではJavaコンポ―ネントと自由に対話できるためコンポーネントをつなぎ合わせる役目を持つ糊言語として適している。
Javaプログラムの柔軟性/拡張性を高めるツールとして
WindowsのVBAやEmacsのelispの例でわかる通り、プログラムにスクリプトによる拡張機能をふくめるとツールの価値は高まる、PnutsはJavaアプリケーションに組み込むためのAPIをもっているため、開発者は自分のプログラムに対して拡張機能を持たせることが容易である。
まだまだいろいろな分野での応用が考えられるがとりあえずこれくらいにしておこう。
もっと詳しい紹介は本家サイトで見よう。

まずはダウンロード

本家サイトからダウンロードすることができる。ここでは、バイナリだけではなくソースコードや日本語ドキュメントを手に入れることができる。Windowsな人間はzipフォーマットをUNIXな人間はtar + gzipフォーマットのバージョンをダウンロードしよう。(実はどちらも中身はいっしょ)

インストール

とくに作業は必要ない。適当なディレクトリにダウンロードしてきたファイルを展開する。すでに起動用のShellスクリプトやバッチファイルが存在することを確認することができる。

基本設定

WindowsユーザならコマンドプロンプトでPnutsを展開したディレクトリに移動してpnuts.batと打ち込んで挙げればこういう画面が立ち上がるはず。Unixユーザでもだいたい同様。そのディレクトリにあるお好みのpnuts.*シェルスクリプトを実行する。

Windows版のときの起動画面
E:\kanno\oed\pnuts\pnuts_1>pnuts
Copyright (c) 1997-2000 Sun Microsystems, Inc. All rights reserved.
Pnuts version 1.0rc1, 1.3.0_02 (Sun Microsystems Inc.)
>

もっといろいろな設定が必要になってきた場合はWindowsユーザならpnuts.bat、UNIXユーザならpnuts.bashもしくはpnuts.kshをカスタマイズするか自分用の起動スクリプトを作成する。
この対話型のシェル上でPnutsスクリプトを打ち込んで直接結果を取得することができる。この対話型シェルを終了する際にはexit()と入力する。

グラフィカルな対話型ツール

ちなみにグラフィカルな対話型ツールも用意されている。このツールを使いたい場合は、pnutool.*(バッチファイルもしくはシェルスクリプト)をつかう。
   

Pnutool

定番のアレと基本

HelloWorld

pnutsコマンドで対話型のシェルを立ち上げる。HelloWorldを表示するためには、単純にprintln("HelloWorld")と打ちこんでEnterすればよい。

HelloWorld
E:\kanno\oed\pnuts\pnuts_1\pnuts_1>pnuts
Copyright (c) 1997-2000 Sun Microsystems, Inc. All rights reserved.
Pnuts version 1.0rc1, 1.3.0_02 (Sun Microsystems Inc.)
> println("HelloWorld");
HelloWorld
null
>

Pnutsでコードを記述する際には、Javaと違ってクラスの定義等をする必要はない。したがってJavaのようにstaticなメソッドmain()の記述も必要ない。ユーザはやりたいことを、すぐに直接記述することができる。
一応ワンライナーの機能もある。ただしPnutsの起動時にはJava自体の起動も必要なので遅い。。さくっとは使えない。ワンライナー用としては使いにくい。
回避策として個人用Pnutsサーバというものが提供されている。ただし今回は説明を省く。

ワンライナーの例 Windows版
E:\kanno>pnuts -e print(\"HelloWorld\");
HelloWorld

基本的な文法

それでは基本的な使い方から学習していこう。

下準備

全部対話型のシェルで操作してもよいが、間違えたりしたとき面倒だったりするので、このセクションではスクリプトはファイルに保存して実行することにする。
pnutsではスクリプトは*.pnutという名前で保存することが多い。たとえばhello.pnutに以下のように記述しておいて、それをクラスパスの通ったディレクトリに置いておく。

hello.pnut
// はコメントとして使える
// /* */ もいける
println("HelloWorldだっつの")

ここでPnusのシェル上からload("スクリプトのファイル名")を実行することによってスクリプトが読み込まれ実行される。また拡張子が.pnutの時は拡張子を省略してもよい

loadの例 Windows版
E:\kanno\oed\pnuts\pnuts_1>pnuts
Copyright (c) 1997-2000 Sun Microsystems, Inc. All rights reserved.
Pnuts version 1.0rc1, 1.3.0_02 (Sun Microsystems Inc.)
> load("hello")
HelloWorldだっつの
null
>

基本的な文法

Pnutsの文法は、Javaにくらべて非常に簡単。JavaよりもJavaSctiptに近い(互換性はない)
制御構文などの基本的な部分はJavaとかJavaSctiptと大体一緒なので覚えやすい。代表的なものを紹介する。きちんと調べたくなってきたら公式ページでドキュメントをダウンロードしよう。
ここでは上記ホームページにのっていた例題をいくつか紹介する。

if文の例
foo = [1, 2, 3]
if (foo.length > 5){
    1
} else if (foo.length > 4){
    2
} else {
    3
}
//答えは    ==> 3

//ifにはこんな書き方もある(if文自体が結果として値を返す)
value = if (foo.length > 3) 1 else 2
//value  ==> 2

繰り返し
//while文
sum=10
while (sum > 0){
  sum = sum - 1
  println(sum)	
}
//for文
for (i=0 ; i<10 ; i++){
  println(i)
}

繰り返し(お勧め)
sum = 0
foreach i [1, 2, 3] {
  sum = sum + i
}
//sum  ==> 6


sum = 0
vec =class java.util.Vector()  //オブジェクトの生成をあらわす構文。次のセクションで解説
vec.addElement(1)
vec.addElement(2)
vec.addElement(3)
foreach i (vec.iterator()) {
  sum = sum + i
}
println(sum)
//sum  ==> 6

関数について

Pnutsではクラスの定義ができない。Pnutsの考え方というか態度としては、「クラスが必要ならJavaで定義すべし」ということになる。
そのかわりPnutsでは関数を定義することができる。Pnutsにおける関数は、「ファーストクラス」のオブジェクトであり関数自体をオブジェクトのように取り扱うことができる。関数の引数に関数を渡してあげたり、関数の戻り値として関数を返すことができる。

関数の定義と呼び出し
//関数の定義

function add( a ,b) {
  return a+b;
  // return文を省いてもよい。最後に評価された値が返される
}

//関数の呼び出し
sum = add( 10 ,40) 
println(sum)   // ->50

以下はPnutsの関数がファーストクラスのオブジェクトであることを利用して、Ruby風イテレータを実現した例である。

関数の定義と呼び出し
//Ruby or Smalltalk風イテレータ
function each_do(array , func ) {
  foreach item (array) {
        func(item)
  }
}

//引数に受けわたすための関数の準備
function printer(item) {
   println(item)
}

//呼び出し
list = [10, "いろいろ" , 45 , 123 ]

each_do(list,printer) //実行!!


//匿名の関数もつかえる
each_do(list, function(item){ println(item)})

この例でわかるように、関数の引数として関数を渡すことによって柔軟な処理を実現できる。

Javaオブジェクトの呼び出し

Pnutsの最大の利点は、スクリプトからJavaのオブジェクトに自由にアクセスして操作することができる点である。JavaAPIで提供されているものや、その他さまざまなクラスライブラリをスクリプトから利用することができる。
このセクションの例ではオブジェクトを操作する感覚を伝えるために、対話型のシェル上で作業を行っている。

Javaオブジェクトの生成

Pntusでは、Javaオブジェクトの作成時にnew演算子を「使わない」ことに注意する。オブジェクトの生成は、class <パッケージ名を含めたクラス名()>のように記述する。
またPnutsでは、ある変数がクラスをあらわしているのかオブジェクトなのかはっきりインタプリンタに伝えるためにclassキーワードを使う。コンパイルという作業がないPnutsでは、スクリプトに出てきた変数がオブジェクトへの参照を表すかクラスを表すかどうかを決定できないためである。

オブジェクトの生成
C:\home\kanno\oed\pnuts_1>pnuts
Copyright (c) 1997-2000 Sun Microsystems, Inc. All rights reserved.
Pnuts version 1.0rc1, 1.3.0_02 (Sun Microsystems Inc.)
> aFrame= class java.awt.Frame()
java.awt.Frame[frame2,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,resizable,title=]
>
>//もしくはこのように書いてもよい
//Classオブジェクトを取得して変数Frameに代入してから、それを関数のように呼び出している
> 
> Frame = class java.awt.Frame
java.awt.Frame class
> aFrame = Frame()
java.awt.Frame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,resizable,title=]
>

import文

オブジェクトを生成するたびに、パッケージ名を含めたクラス名を記述するのは面倒である。Pnutsでは、Javaのようにクラスやパッケージをimportする機能がある。この機能を使う方がJavaの流儀に近い。

オブジェクトの生成
>import("java.awt.*")
null
> aFrame=Frame()
java.awt.Frame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,resizable,title=]
>

メソッドの呼び出し

インスタンスメソッドの呼び出しはJavaとほとんど一緒である

メソッドの呼び出し
>import("java.awt.*")
null
> aFrame=Frame()
java.awt.Frame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,resizable,title=]
> aFrame.show()                //Windowが表示される
null
> aFrame.setSize(300,200)      //Windowのサイズが変更される
null
> aFrame.dispose()            //Windowが消去される
null
>

staticメソッドのよびだし

  staticメソッドの呼び出しは レシーバ :: メソッド と記述する

staticメソッドの呼び出し
>Math::sqrt(3)
1.7320508075688772
> Math::random()
0.5332636966391512
>

 

インスタンス変数・クラス変数へのアクセス

基本的にはメソッドの時と要領は一緒である。インスタンス変数へのアクセスには、.をつかいクラス変数へのアクセスには:: を使う。  

フィールドへのアクセス
>Point = class java.awt.Point
java.awt.Point class
> pt = Point(10, 20)
java.awt.Point[x=10,y=20]
> pt.x         //参照
10
> pt.x=5   //代入
5

>//staicなフィールドの場合
> blue = class java.awt.Color::blue
java.awt.Color[r=0,g=0,b=255]
>

習うより慣れろ!! サンプル

簡単な例

いままでの復習を兼ねて簡単なダウンロードスクリプトを書いてみる。

ダウンロードスクリプト
import("java.io.*");
import("java.net.*");
EOF=-1;      

/**cat (inputSteram , outputSteram)で
inputの内容をoutputに書き込む
同じことはPnutsで用意されている関数read(in,out)でもできる
 */
function cat(in , out) {
    bout= BufferedOutputStream(out);
    while( (c = in.read()) != EOF ) {
        bout.write(c);
    }
    bout.flush();
}

/**
urlStrの内容はhttps://からはじめてください。
download(urlStr, fileName)
 */
function download(urlStr, fileName){
    
    //proxyを使うとき
    //urlObj=URL("http" , "your.proxy.server.name", 8080 ,urlStr); 
    
    urlObj=URL(urlStr);  //java.net.URLの作成
        
    println(urlObj);
    in = urlObj.openStream(); 
    
    out = FileOutputStream(File(fileName));
    cat(in, out); //inの内容をoutに書き込む
    
    out.close();
    in.close();
}

実行例

  

>load("downloader.pnut")
function download(urlStr,fileName)
> download("https://www.ogis-ri.co.jp","sample.html")
https://www.ogis-ri.co.jp
null
>

//ここでsample.htmlというファイルにURLで示したHTMLがセーブされる

解説

上記スクリプトではjava.net.URLクラスを利用して,指定したURL上のHtmlをダウンロードする。(大体同じような内容のスクリプトが、Pntusの配布パッケージのサンプルの中にも含まれている。) Pnutsの基本的な機能とJavaのAPIのみを使ってるので、ほとんどの環境(JDK1.1相当以上)で動作するはずである。
ポイントは、
といったところ。
もう一点。本当はPnutsでは行末の;は必要ない。ただし入れてもよい。(好き好きということ)

おなじものをJavaで書くと

先程のスクリプトをJavaで書き直してみる。

Java版ダウンロードプログラム
import java.io.*;
import java.net.*;

public class Downloader {
    public static final int EOF=-1;

    /**
    cat (inputSteram , outputSteram)でinputの内容をoutputに書き込む
    */
    public void cat(InputStream in , OutputStream out) throws IOException {
        BufferedOutputStream bout= new BufferedOutputStream(out);
        int c;
        while( (c = in.read()) != EOF ) {
            bout.write(c);
        }
        bout.flush();
    }
      
    /** 
    urlStrの内容はhttps://からはじめてください。
    download(urlStr, fileName)
    */
    public void  download(String urlStr, String  fileName){
        try {
            URL urlObj=new URL(urlStr); 
            InputStream in = urlObj.openStream(); 
            OutputStream out =new  FileOutputStream(new File(fileName));
            cat(in, out); 
            out.close();
            in.close();
        } catch (Exception e ) { //楽してます。ごめんなさい
            e.printStackTrace();
        }
    }

    public static void main(String[]  args) {
        if(args.length==2){
            Downloader d = new Downloader();
            d.download(args[0],args[1]);
            
        }

    }
}

かなり適当に書いているがちょっと面倒。そもそもこの程度の作業を行うのにわざわざクラスを定義する必要性がないような気がする。

比較

単純に「こういうことがしたい!」という要求を満たすプログラムを記述するときに、Javaでは必ずクラスを記述して、メソッドを記述してXXXXというステップを踏まなければならない。一方Pnutsではやりたいことを直接記述できる。Pnutsのコードは、実際動作してくれる擬似コードを書いているような感覚で気軽に書くことができる。
また今回の例ではPnutsの処理系が提供してくれている関数を(意図的に)あまりつかっていない。これらの関数を使えばさらに楽にプログラムを作成することができる。
あとで再利用性が必要になってきて、クラスを定義したくなった場合はどうすればよいか。そういうときは、あらためて必要なクラスを定義すればよい。今回の例のようにPnutsスクリプトからJavaに落とす作業は(メソッド内部のロジック部分は基本的に一緒なので)簡単である。
Pnutsを日常的に使っているとPnutsでプロトタイプを作ってからJavaで実装というスタイルが自然に生まれるかもしれない。例えば今回筆者が原稿を書く際には、プロキシを使った際のURLオブジェクトの生成に関する部分が(ドキュメントが少ないため)理解しづらかったが、Pnuts上でトライアンドエラーで試しているうちに使いかたを理解することができた。

まとめ

やりのこしたこと

今回の記事では、まずは入門編というところを目指したために、以下のトピックは記載されていない。
GUIスクリプティング
PnutsではGUI画面をスクリプトで簡単に記述するために専用のLayoutマネージャなどを備えている。画面部分はスクリプトを使い、ロジック部分をJavaで書くことが非常に楽チン
Pnutsをサーブレットとして使う
Pnutsをつかってサーブレットを記述できる。コンパイルやインストールをすることなくWebアプリの挙動を変更することができる。
モジュールetc.
上に述べた機能以外にも、Pnutsでは正規表現の利用やWinodowsのCOMオブジェクトの取り扱いなどさまざまな拡張機能が用意されている。これらの機能はmoduleという単位になっていて、スクリプトのなかでuse(モジュール名)宣言することによって使用できるようになっている。
これらの機能を調べたくなった場合は、本家サイトを参考にして欲しい。
もしくはこの記事が連載化されれば紹介する予定である。

スクリプト言語を使った開発サイクル

下の模式図は、スクリプト言語と通常のコンパイラ言語を両方使いながら行うハイブリット開発を示している。

模式図

Python入門の巻末に記述されているものからアイディアを頂いている。この図は、開発の時間が経つにともなってスライダーが右から左へ、もしくは左から右へスライドしていくイメージで描かれている。
この図によると開発のタイプは2種類である。

左から右へスライダーをずらしていく
開発の初期段階ではPnutsでプロトタイプを記述し開発を高速にすすめる。ある程度開発が進むにしたがってコンポーネント化による再利用性が必要な部分や、パフォーマンスの要求が厳しい部分をJavaに書き換えていく
右へ左へスライダーをずらしていく
最初Javaアプリケーションとして記述されたものから、プラグインのように動的に振る舞いを変更したい部分や、頻繁に変更要求がでる部分をPnutsで置き換えていく
このようなスクリプト言語とコンパイラ言語によるハイブリットなアプリの例としては、(Pnutsの例ではないが)最近ではMozillaが有名である。最初は単なるアプリケーションだったものが、最終的にはGUI画面の大部分をXMLベースのスクリプト言語で置き換えてしまった。それにより開発サイクルが早くなったかどうかは定かではないが、少なくとも多言語化や見栄えの変更などは、はるかに楽になっているはずである。(新規の貢献者は参入しやすくなった)
このハイブリットな開発スタイルが実際の開発にどこまで応用できるかどうかは分からないが、少なくてもエンジニアの個人的なツールを開発したりする際には非常に役立つ考え方ではないだろうか。

まとめ

スクリプト言語は仕事で直接使わなくても、日常的なユーティリティを作成する際などに便利な代物である。また近年はスクリプト言語に対する評価が高まっており、RubyPythonのような言語も登場してきている。Rubyと同じように日本発のスクリプト言語であるPnutsだが、イマイチRubyほどメジャーな言語ではないようだ。やはりJavaを前提をしている点で敷居が高いからだろうか。(Java環境の導入が必須であるため)
しかし特にJava関係の開発者なら、とりあえずダウンロードしておくことをお勧めする。Pnutsの利点は言語単体の機能にあるのではなく、Javaで作成された豊富なクラスライブラリを手軽に使える点である。
Java開発者としては、単に仕事としてJavaを使うだけではなく日常的な作業を自動化する際に(Pnuts越しに)Javaのパワーを生かせたら、非常に素敵ではないだろうか。

リソース

今回の言語を書くにあたって以下のサイト(およびそれらをを提供している方々)にお世話になりました。このページだけ「ですます」調で失礼します。

© 2001 OGIS-RI Co., Ltd.
HOME オージス総研[オブジェクト第一事業部] HOME TOP オブジェクトの広場 TOP