今更だが、日記を始めることにした。主にGroovy、PnutsなどのJava系スクリプト言語関係のことを書く予定。
以前にちょっとだけ触ってみたNiceについて調べてみる。 Niceは、Javaをベースに高階関数、マルチメソッド、パラメータ型、キーワード引数、省略可能引数、Option Typesなどの機能を追加した言語で、処理系は、Java VM用のコードを吐くコンパイラとなっている。
マルチメソッド、キーワード引数などは、他の言語でもよく見かけるが、 Option Typesは、他の言語ではあまり見かけない機能だ。Option Typesとは、値としてnullを許す型で、それだけ見ると、珍しくもなんともないのだが、Niceでは面白いことに、変数の型はデフォルトでnullを代入できないのだ。例えば、以下のようなコードを書くと、sはnullを代入できない型になり、もしsにnullが代入され得るようなコードを書くと、コンパイルエラーになる。
let String s = "Hello";//nullは代入できない
もし、nullを代入可能にしたければ、型名の頭に'?'を付けて、次のように宣言する。
let ?String s = "Hello";//nullが代入可能
この機能は、大変面白い機能なんじゃないかと思う。例えば、Mapにキーを与えて値を取得するとき、返って来た値がnullかどうかをチェックする必要がある場合は多いが、この機能があれば、もし仮にnullかどうかのチェックを忘れたとしても、コンパイルエラーになってくれる。
しかし、このチェック機能、どこまでちゃんと働くのだろうか。それを確かめるために、以下のコードを書いて実験してみることにした。このコードでは、if文の中では、valueは絶対にnullになり得ないが、それをコンパイラが判断できるかどうか。
void main(String[] args){
let Map map = new HashMap();
map.put("A", 0);
map.put("B", 1);
map.put("C", 2);
?int value = map.get("E");
if(value != null || false){
int value2 = value;
}
}
このコードをコンパイルしてみると、以下のエラーメッセージが出力された。
nice.lang: parsing example: parsing example: typechecking
C:\programs\nice\.\example\option_type.nice: line 8, column 21: Unused local variable value2
C:\programs\nice\.\example\option_type.nice: line 8, column 21: The value value cannot be assigned to value2 because it might be null.
To allow value2 to contain the null value, it should be declared as: ?nice.lang.int value2
どうやら、論理式の中身までは見ておらず、nullかどうかを判断する式(value != null)と他の式が、||でつながれているかどうかだけを見ているようだ。となると、&&の場合はどうなるだろうか。以下のコードも、先ほどと同じくif文の中ではvalueはnullになり得ないが、先ほどと違って、演算子の種類(&&か||か)だけを見ればそれを判断可能になっている。
void main(String[] args){
let Map map = new HashMap();
map.put("A", 0);
map.put("B", 1);
map.put("C", 2);
?int value = map.get("E");
if(value != null && false){
int value2 = value;
}
}
このコードをコンパイルすると、今度は、何もエラーが出ずにコンパイルを通った。やはり、演算子の種類を見て判断しているようだ。
Pnuts1.1には、ジェネレータという機能がある。これは、関数定義の中に、'yield' 式を入れることで、その関数を呼び出したときに、関数が実行されるかわりにジェネレータを返すというもので、このジェネレータを、for文中で、
for(変数名 : ジェネレータ){
...
}
のように使うと、yieldで評価した式の値が1つずつ変数に代入され、 その度に...が実行される。例えば、range()関数は、第1引数から 第2引数までの値を順にyieldするジェネレータで、次のようにして 使用する。
for(i : range(1, 3)) print(i)
実行結果は以下のようになる。
C:\programs\pnuts>pnuts range.pnut 123
さて、このジェネレータ、Pnutsのライブラリでは、主にループ のために使用されているが、何もループのためにしか使えないわけではない。例えば、PnutsBlogでは、ファイルを確実にcloseするために使う例が載っている。
また、他の応用例として、ソートの比較条件を指定するために使うことも考えられる。以下のコードは、ソートアルゴリズム中で、配列の要素をyieldした結果を元にソーティングを行うようにすることで、for文のブロックで比較条件を指定できるようにしている。
function bubble_sort(lst){
if(lst instanceof Object[]){
size = lst.length
}else{
size = lst.size()
}
for(i = size - 1; i >= 0; i--){
for(j = 0; j < i; j++){
pair = $()
pair.left = array[j]
pair.right = array[j + 1]
cmp_result = yield(pair)
if(cmp_result > 0){
tmp = lst[j]
lst[j] = lst[j + 1]
lst[j + 1] = tmp
}
}
}
}
array = [5, 3, 2, 4, 1]
for(pair : bubble_sort(array)) pair.left - pair.right
println(join(" ", array))
for(pair : bubble_sort(array)) pair.right - pair.left
println(join(" ", array))
実行結果は、以下の通り。
C:\programs\pnuts>pnuts sort.pnut 1 2 3 4 5 5 4 3 2 1
プログラミング言語処理の授業課題である、tiny Cのパーサを書く。といっても、tiny Cを書く課題は、まだ先の話だが。書くためのプログラミング言語は何でもいいと言っていたので、JavaCCを使うことにした。yaccを使ってもいいのだが、Cで書くのが面倒くさいので。
JavaCCはLLなパーザジェネレータで、JJTreeというJavaCCに付属のプリプロセッサを使えば、構文木を自動生成することもできるが、今回はJJTreeを使わずに、JavaCCだけで書くことにした。その理由はいくつかあるが、JJTreeの生成する構文木の形式では、子ノードにアクセスするのにインデックスを使わなければならないというのが、一番大きなところだ。例えば、if文を表すノードから条件式を表すノードを取得するとき、
node.getCondition()
のような形で書けると嬉しいのだが、JJTreeで生成されたノードでは、
node.jjtGetChild(0)
のような形で書かなければいけない。これは、可読性の面からできれば避けたい。というわけで、JavaCCで書いてみると、さすがに文法が簡単なので、わりとすぐに文法定義ファイルは完成した。
ちなみに、作った文法定義ファイル(tinyc.jj)はこちら。
この文法定義ファイルには、構文木を構築するコードが入っていないが、それはこれから追加する予定。
Pnuts1.1のジェネレータを使って、foldlを実装してみる。コードは 以下の通り。
function foldl(init, lst){
result = init
for(e : lst){
pair = $()
pair.x = result
pair.y = e
result = yield(pair)
}
}
実行するときは、以下のように書く。
sum = for(p : foldl(0, [1,2,3,4,5,6])) p.x + p.y
println("sum:"+sum)
実行結果は以下の通り。
C:\programs\pnuts>pnuts foldl.pnut sum:21
しかし、こんなものを使うくらいなら、普通に、functionalモジュールのfoldlを使った方が良い気もする。こっちの利点といったら、少しだけタイプ量が減るくらいか。
友人が使っているサーバ用マシンが遅すぎるということで、家で余っていたマシンをあげることに。入れ替え後は大分速くなったようで、なにより。
_ IKeJI [それがどういう機能で、なぜエラーが出るのかを説明してくれた方がユーザーフレンドリーです。]
_ mizu [説明を追加してみました。]