バインド更新


バインド(bind)とは

あなたがバインド式を書いた場合、

def x = bind someExpression; 

someExpressionが変化したとき、x はそれに合わせて更新されるという意味です。以上です。 ほとんどの場合では、これがあなたが知っているべきすべてです。 しかし場合によっては、どのように更新が正確に起こるかを知るべきで、 そして場合によってはsomeExpressionの変化が何を意味しているのかを。 これらは以下で論じます。


更新時に何が再計算されるのか

バインドでは、最小限の再計算がなされます。これは限られた状況だけです。 例えば、オブジェクトがバインドで生成されます。これはオブジェクトの固有性のためです。 新しいオブジェクトが生成されるか否かが問題になります。

例を見てみましょう。

def sum = bind expr1 + expr2; 

expr2の値が変化して、加算が再度なされても、expr1は再計算されません。 その値は保存されていて、単純に再取得されます。

var y = 3; 
function ten() : Integer { 
   println("Called ten");
   10 
} 
def sum = bind ten() + y; 
println(sum); 
y = 7; 
println(sum); 

印字結果:

Called ten
13 
17

次に注意してください。 関数ten()はsumの初期値の計算で呼び出されますが、yに7が設定されたとき、 関数ten()は再び呼び出されることはありません。(なぜなら変化しなかったからです。) その値は記憶されていて再使用されたのです。


条件式

def x = bind if (condExpr) expr1 else expr2;

もしcondExprの値が変化したら、if文のどちらの分岐が評価されて再計算を引き起こすのかを、 condExprが変化するたびに切り替えます。分岐の前の値は保存されません。 もしcondExprがtrueなら、expr1に依存するものに変化が起きて再計算がなされます。 しかし、expr2の計算がしませんし、expr2に依存するものに変化を与えません。 具体的に言うと、condExprがtrueなら、expr1だけが実行され、expr2は実行されません。 逆もまた同様です。


for式

def newSeq = bind for (elem in seq) expr;

seqが変化しても、seqの中の平静な要素に結びつくnewSeqの要素は再計算されません。 これは、もしseqに要素が挿入されたら、その要素にexprを要素に適用した結果が newSeqの該当する位置に挿入され、他の要素は再計算されません。 このルールには例外があります。もしexprがindexof elemを使うと、 インデクスが変化した要素は更新されるべきですが、しかし最小限の更新ルールが適用されます。

例:

var min = 0;
var max = 3;
function square(x : Integer) : Integer { x*x }
def values = bind for (x in [min..max]) square(x);  ①
println(values);
max = 5;  ②
println(values);
min = 1;  ③
println(values);
min = 0;  ④
println(values);

印字結果:

[ 0, 1, 4, 9 ]  ①
[ 0, 1, 4, 9, 16, 25 ]  ②
[ 1, 4, 9, 16, 25 ]  ③
[ 0, 1, 4, 9, 16, 25 ]  ④

再計算はどうなるか:

① まず0から3までの二乗が計算されます。
② 次に4と5が計算されます。(maxが変化したとき0から3は再計算されません。)
③ 次に0の二乗が削除されます。(どの値も再計算されません。)
④ 次に0の二乗が再び追加されます。(これは再計算が求められます。)

この動作はinsertとdeleteが使われるときも同様です。

[To do: indexof]

[To do: on-replace句の例を追加する]


ブロック式

ブロック式は中括弧で囲まれた式のリストです。ブロック式の値は最後の式の値です。 バインドの中では、ブロック式の最後の行以外で使える式は、変数の定義(def)だけです。 初期のいくつかのバージョンはvarを許しましたが、これは許されないことに注意してください。 また、割り当て(増加と減少を含み)もバインドの中では禁止されていることに注意してください。 バインドのブロック式はこのような形になります。

bind { 
   def a = expr;  
   def b = expr;  
   def c = expr;  
   expr 
} 

なぜなら、バインド式の変化は更新を引き起こし、その更新は最小限であり、 変数が効果的に結び付けられていることで理解できます。

私達は、while,insert,deleteなどがバインドのブロック式では使えないことを推定できます。 それらは、最後の行で発生でません。なぜならそれらは変数宣言ではないからであり、 Void型であり、値を持たず、バインドされません。このように最後の行で発生できないのです。


関数とメソッドの呼び出し

(訳注:関数はJavaFXを、メソッドはJavaを指しているらしい。)

def val = bind foo(a, b);

非バインドな関数は、バインドのキーワードでは開始されません。 Javaのメソッドの呼び出し、または非バインドなJavaFXの関数については、 その関数は引数のどれから変更されたら再実行されます。 ですが、関数のボディはブラックボックスであり、 引数の向こう側にある依存する領域は再計算されません。

例:

class Point {
  var x : Number;
  var y : Number;
}

var scale = 1.0;
function makePoint(x0 : Number, y0 : Number) : Point {
  Point {
    x: x0 * scale
    y: y0 * scale
  }
}

var myX = 3.0;
var myY = 3.0;
def pt = bind makePoint(myX, myY);
println(pt.x);
myX = 10.0;   ①
println(pt.x);
scale = 2.0;  ②
println(pt.x);

印字結果:

3.0 
10.0   ①
10.0   ②

① 引数 myX の変更が makePoint の再呼び出しを引き起こします。
② しかし、関数 makePoint はブラックボックスなので、scaleの変更は更新を引き起こしません。バインド関数が役に立ちます。

バインド関数

バインド関数はそのボディにバインドで実行されるブロック式を持ちます。 (バインドのブロック式における前述の制約を持ちます。) バインド関数が関連付けられたとき、ボディの式の値の変化は、引数に限らずに、更新を引き起こし、 そして引数の変化は関数に見られる通りです。 よって、前述の関数 makePoint であれば、むしろバインド関数になります。

bound function makePoint(x0 : Number, y0 : Number) : Point { ...

scale の変化は20.0への更新を引き起こします。 myX が変化したらx0 * scaleが再計算され、y0 * scaleは計算されないことに注意してください。


オブジェクトリテラル

オブジェクトリテラルは単純な演算(+,など)や非バインドな関数のように振舞います。 それはつまり、オブジェクトリテラルの引数の一つが変化したら、 再実行されて新しいインスタンスが作られるということです。

def pt = bind Point { 
   x: myX
   y: myY  
}

myX が変化したら新しいPointのオブジェクトが作られます。 これは確実にあなたが不変のオブジェクトとして必要としているものです。

あなたが、新しいPointを生成せずに、xの値にmyXの値を追わせたいならどうしますか。 その場合、インスタンス変数の初期化式をバインドにしてください。

def pt = bind Point { 
   x: bind myX
   y: myY  
}

今、myXが変化したら、Pointであるptのインスタンス変数 x は更新されます。 しかし新しいPointは生成されません。なぜなら、オブジェクトリテラルのインスタンス変数の初期化式は 変わっていないからです。(x は変わらず、常にmyXにバインドします。) myYの変化はいまだに新しいPointの生成を引き起こします。

よって、あなたがこのサンプルに求めていることは、そのPointにmyXとmyYを追わせることでしょう。

def pt = Point { 
   x: bind myX
   y: bind myY  
}

ここでptは常に同じPointとして残ります。 もはや、ptの初期化式にバインドが必要ないことに注意してください。 なぜなら依存するものがないからです。


Home