日記帳

日記です。

40分ではわかりせん Java プログラマのための JavaFX Script 入門

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

バインディングの話その2.間違いとかあったら教えてください.

変数へのバインディング

変数に変数をバインディング
var x = 2;
var y = bind x;
println(y); // => 2
x = 3;
println(y); // => 3

だからなんなんだという感じですね…

変数に式をバインディング
var x = 2;
var xx = bind x*x;
println(xx); // => 4
x = 3;
println(xx); // => 9
バインドされた変数は代入不可
var x = 2;
var xx = bind x*x;
xx = 9; // compile error!
com.sun.javafx.runtime.AssignToBoundException: Cannot assign to bound variable
        at com.sun.javafx.runtime.location.IntVariable.setAsInt(IntVariable.java:115)
        at a.javafx$run$(a.fx:3)
代入不可なら定数でいいよね
var x = 2;
def xx = bind x*x;
println(xx); // => 4
x = 3;
println(xx); // => 9

問題なく動きます.つまりdef式で定数として定義されているものでもバインドされている場合には値が変化することがあるということですね…

if式のバインディング
var x = 10;
var y = bind if(x mod 2 == 0) "Even" else "Odd";
println(y); // => Even
x = 11;
println(y); // => Odd
for式のバインディング
var a = [1..5];
var b = bind for(i in a) i*i;
println(b); // => [ 1, 4, 9, 16, 25 ]
insert 6 into a;
println(b); // => [ 1, 4, 9, 16, 25, 36 ]

forはシーケンスを返す式なので当然バインディング可能です.

シーケンス式のバインディング
var one:Object = 1;
var a = [one, 2, 3];
var b = bind [one, 2, 3];
println("a:{a.toString()}, b:{b.toString()}"); // => a:[ 1, 2, 3 ], b:[ 1, 2, 3 ]
one = "one";
println("a:{a.toString()}, b:{b.toString()}"); // => a:[ 1, 2, 3 ], b:[ one, 2, 3 ]

シーケンス式も式なので当然バインドできます.

シーケンスを一部分だけ反転してみる
var a1 = [1..5];
var a2 = [6..10];
var b = bind [a1, a2];
println(b); // => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
a1 = reverse a1;
println(b); // => [ 5, 4, 3, 2, 1, 6, 7, 8, 9, 10 ]

ネストされたシーケンスを含むシーケンス式のフラット化を利用したバインディング

bind with inverse で相互バインディング
var a = 10;
var b = bind a with inverse;
println("a:{a}, b:{b}"); // => a:10, b:10
a = 20;
println("a:{a}, b:{b}"); // => a:20, b:20
b = 30;
println("a:{a}, b:{b}"); // => a:30, b:30

with inverse を付けてバインドするとバインドされた変数の変更を元の変数に反映させることができます.GUIオブジェクト間で値を連動させる場合などに便利ですね.

bind with inverse では式のバインディングは不可
var a = 10;
var b = bind a * a with inverse; // compile error
a.fx:2: Expression not supported for bind with inverse.
var b = bind a * a with inverse;
               ^

まぁ当然ですね.

関数呼び出しを含むバインディング
function square(x:Integer) {
	println("square({x}) is called.");
	return x*x;
}
var x = 2;
def xx = bind square(x);
println(xx); // => 4
x = 3;
println(xx); // => 9

関数呼び出しも式なので問題なくバインドできます.

square(2) is called.
4
square(3) is called.
9

変数 x に 3 を代入した時点で2回目の関数 square() が呼ばれているのがわかります.

結合関数の呼び出しのバインディング
var a = 2; var b = 3;
bound function func(x:Integer) { return a * x + b; } // 関数 y = ax + b

var y = bind func(2);
println(y); // => 7  (y = 2x + 3 = 2 * 2 + 3 = 7)

a = 4; b = 6; // y = ax + b の定数部分を変更して関数を y = 4x + 6 にしてしまう
println(y); // => 14 (y = 4x + 6 = 4 * 2 + 6 = 14)

キーワード bound を付けた関数は結合関数となります.結合関数の呼び出しをバインドした場合,その関数内部で参照している変数の変更に対しても再度呼び出されるようになります.

引数をバインドするだけならば結合関数にする必要はありません.

関数オブジェクトを返す関数の呼び出しのバインディング
function generate_func(a:Integer, b:Integer){
	function(x:Integer){ a * x + b; } // y = ax + b
}
var a = 2; var b = 3;
var f = bind generate_func(a, b);
println(f(2));

a = 4; b = 6;
println(f(2));

結合関数なしで上と同じようなことをしてみたもの.変数 a や b の変更により関数オブジェクトを持つ変数 f が更新される.

関数式で結合関数は作れない?
var a = 2; var b = 3;
var f = bound function(x:Integer) { return a * x + b; } // compile error!

何か作る方法があるのかしら?

関数式で定義した関数の呼び出しのバインディング
var f = function(x:Integer):Integer { 2 * x + 3 };
var x = 2;
var y = bind f(x);
println(y); // => 7

x = 4;
println(y); // => 11

f = function(x:Integer):Integer { 4 * x + 6 };
println(y); // => 11

関数オブジェクトを保持している変数 f を変更しても関数呼び出しをバインドしている変数 y は変更されません.

まとめ

JavaFX Script の立ち位置は Java コンポーネントを利用してアプリケーションに仕立てるグルー言語なわけです.バインディングはグルー言語の結び付ける物としての役割を果す上で大変便利な機能です.つまりバインディングこそが JavaFX Script であり JavaFX Script こそがバインディングであると言っても過言ではないくらいですね(過言です).