日記帳

日記です。

20分でわかったらうれしい Java プログラマのための JavaFX Script 入門

http://d.hatena.ne.jp/sa-y/20090204 の続き.

シーケンスの話.熟練した JavaFX プログラマならシーケンスだけでご飯3杯はいけるらしいです.

シーケンスの作成

明示的シーケンス式でシーケンスを作成
var a = [ 1, 2, 3, 4 ];
println(a); // [ 1, 2, 3, 4 ]

「[]」にコンマ区切りで要素を並べただけ.

各要素の型は異っていてもよい
var a = [ 1, "Two", 3, "Four" ];
println(a); // [ 1, Two, 3, Four ];
要素の型を明示的に指定してコンパイラに型チェックさせることも可能
var a:Integer[] = [ 1, "Two" ]; // compile error!
a.fx:1: incompatible types
found   : String
required: Integer
var a:Integer[] = [ 1, "Two" ];
範囲式で整数の範囲のシーケンスを作成
var a = [0..5];
println(a); // => [ 0, 1, 2, 3, 4, 5 ]
範囲式にステップを指定
var a = [0..5 step 2];
println(a); // => [ 0, 2, 4 ]
降順の範囲式
var a = [5..0 step -2];
println(a); // => [ 5, 3, 1 ]
シーケンススライス
var a = ["One", "Two", "Three", "Four", "Five"];
var sub = a[1..2]
println(a); // => [ One, Two, Three, Four, Five ]
println(sub); // => [ Two, Three ]

既存のシーケンスから指定した範囲の部分シーケンスを作れます.

ブール式でシーケンスを作成
var a = [0..5];
println(a); // => [ 0, 1, 2, 3, 4, 5 ]
var odd = a[i| i mod 2 == 1]
println(a); // => [ 1, 3, 5 ]

既存のシーケンスから条件に合う要素だけを拾い出してシーケンスを作れる.

reverse 演算子で反転したシーケンスを作成
var a = [0..5];
var ra = reverse a;
println(a); // => [ 0, 1, 2, 3, 4, 5 ]
println(ra); // => [ 5, 4, 3, 2, 1, 0 ]
for式でシーケンスを作成
var a = for(i in [0..5]){ i * i }
println(a); // => [ 0, 1, 4, 9, 16, 25 ]

for式は各ループの評価結果を要素とするシーケンスを作ってそれを返す.

for式で Void を返すような場合があるとコンパイルエラー
var a = for(i in [0..5]) {
	if (i mod 2 == 0) i*i;
}
a.fx:1: 'void' type not allowed here
var a = for(i in [0..5]) {
^
1 error

シーケンスの型

ArraySequence
var a = [ "Hello" ];
println(a.getClass()); // => class com.sun.javafx.runtime.sequence.ArraySequence
IntRangeSequence
var a = [0..9];
println(a.getClass()); // => class com.sun.javafx.runtime.sequence.IntRangeSequence
実はシーケンスは Object ではない
var a:Object[] = [ "Hello" ];
println(a instanceof Object); // => false

Java の配列は Object なのにね…

シーケンスへのアクセス

sizeof 演算子によるシーケンスのサイズの取得
var a = ["One", "Two", "Three"];
println(sizeof a); // => 3
indexof 演算子による要素のインデックスの取得
var a = ["One", "Two", "Three"];
for(element in a){
	println("a[{indexof element}] : {element}"); 
}
a[0] : One
a[1] : Two
a[2] : Three

これで each_with_index() とか map-with-index とかインデックス付きの変種ループをいちいち作らなくてもいいということかしら? 悪くない構文かも.

当然ですが indexof は繰り返し変数にしか指定できません.

インデックスを指定して参照
var a = ["One", "Two", "Three", "Four", "Five"];
println(a[1]); // => Two
インデックスを指定して代入
var a = ["One", "Two", "Three"];
println(a); // => [ One, Two, Three ]
a[1] = "Zwei";
println(a); // => [ One, Zwei, Three ]
範囲外のインデックスを指定して参照するとnull
var a = ["One", "Two", "Three"];
println(a[100] == null); // => true
範囲外のインデックスを指定して代入は無効
var a = ["One", "Two", "Three"];
println(a); // => [ One, Two, Three ]
a[5] = "Six";
println(a); // => [ One, Two, Three ]

例外は発生しない.

シーケンスと暗黙の型変換

シーケンス型変数への非シーケンスの代入
var a:String[];
a = "Hello";
println(a); // => [ Hello ]

シーケンス型の属性の初期化の時に括弧2つ分入力が省けます.

ネストされたシーケンスのフラット化
var a = [ "One", "Two", "Three" ];
var b = [ "Four", ["Five", "Six"] ];
var c = [ a, b, "Seven" ];
println(a); // => [ One, Two, Three ]
println(b); // => [ Four, Five, Six ]
println(c); // => [ One, Two, Three, Four, Five, Six, Seven ]

シーケンス型の属性を初期化する際にシーケンスを返す式の羅列を使ったりできるらしい.

var a:Integer[] = [
	[0..5][i|i mod 2 == 0],
	[6..10 step 2],
	for(i in[6..10]) i*2
];
println(a); // => [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ]

ではシーケンスのシーケンスはどう定義すればいいのだろうか? 以下のコードはコンパイルエラーになる.

var a:Object[][]; // compile error!

実はシーケンスはオブジェクトではないのでシーケンスに含めることができないらしい.

シーケンス要素の挿入と削除

insert 式でシーケンスの最後に要素を追加
var a = ["One", "Two", "Three"];
insert "Four" into a;
println(a); // => 
インデックスで指定した要素の前に要素を挿入
var a = ["One", "Two", "Three"];
insert "1.5" before a[1];
println(a); // => [ One, 1.5, Two, Three ]
インデックスで指定した要素の後に要素を挿入
var a = ["One", "Two", "Three"];
insert "2.5" after a[1];
println(a); // => [ One, Two, 2.5, Three ]
範囲外となるインデックスを指定して挿入すると無効
var a = ["One", "Two", "Three"];
insert "101" before a[100];
println(a); // => [ One, Two, Three ]

例外は発生しない.

最後の要素の1つ後のインデックスを指定して,その前に挿入するのは有効
var a = ["One", "Two", "Three"];
insert "Four" before a[3];
println(a); // => [ One, Two, Three, Four ]
delete 式でインデックスで指定した要素を削除
var a = ["One", "Two", "Three"];
delete a[1];
println(a); // => [ One, Three ]
範囲を指定しての削除
var a = ["One", "Two", "Three"];
delete a[1..2];
println(a); // => [ One ]
インデックスや範囲を指定しないと全要素を削除
var a = ["One", "Two", "Three"];
delete a;
println(a); // => [ ]
delete from で指定した要素を削除
var a = ["One", "Two", "Three"];
delete "Two" from a;
println(a); // => [ One, Three ]
delete from で存在しない要素を指定した場合は無効
var a = ["One", "Two", "Three"];
delete "Five" from a;
println(a); // => [ One, Two, Three ]

例外は発生しない.

代入と挿入と削除の実態は新しいシーケンスの作成と変数への代入
var a = ["One", "Two", "Three"];
var oldHash = a.hashCode();
insert "Four" into a;
var newHash = a.hashCode();
println("oldHash:{oldHash}, newHash:{newHash}");
println(oldHash == newHash); // => false

見掛け上は挿入や削除に見えるけど実際には別オブジェクトになっています. つまりシーケンスはイミュータブルであり, 代入や挿入や削除は変更された新しいシーケンスを作成して変数へ代入してくれるシンタックスシュガーでしかないのかな?

var a = ["One", "Two", "Three"];
var alias = a;
a[0] = "1";
println(a); // => [ One, Two, Three, Four ]
println(alias); // => [ One, Two, Three ]

ちゃんと古いシーケンスはそのまま残っていますね.

新規にシーケンスオブジェクトを作成する処理ってことはつまりそれなりに重い処理ってことで扱い注意する必要がありそうです.

まとめ

いまどきのスクリプト言語らしくいろいろな言語がまざってる気がしますね…

シーケンスはイミュータブルだしネストできないしという感じで Java の配列や java.util.List を置き換えるようなものではなさそうです. しかしお手軽だしバインディングと組合せるといろいろ便利なことができそうなのでコレクションAPIとうまく使いわけて使うといいのかもしれません.