Saturday, May 23, 2009

Be careful binding to functions in JavaFX

In JavaFX (I'll call it JFX from now) there is possibility to bind variables. You can bind variable to another variable:
var src = 4;
def dst = bind src;
println(dst);
src = 42;
println(dst);
it'll print:
4
42
Maybe you noticed I used var for one variable and def for another. It could be as well var in both cases. Bind makes dst is updated every time it is read. Here we bound variable to another variable, but we can bind it to any expression:
var factor = 3;
var multiplier = 3;
def dst = bind getValue(multiplier);
println(dst);
multiplier = 5;
println(dst);

function getValue(p:Integer) {
 p * factor;
}
What it gives us?
9
15
Still everything is quite clear, isn't it? First value of dsc is 3*3, which is 9, than multiplier is changed and we get 5 * 3, which is 15. Let's now change factor, not multiplier:
var factor = 3;
var multiplier = 3;
def dst = bind getValue(multiplier);
println(dst);
factor = 5;
println(dst);

function getValue(p:Integer) {
 p * factor;
}
And we get:
9
9
Now this is wrong! First we want 3*3 which is 9, so it is ok, but then we change factor and expect 3*5, which is 15, but we still get 9. What's up? Well, dsc is bound to function. JFX remembers our function was called last time with parameter 3, and returned 9. Second time (second read of dst) it is also called with parameter 3, so JFX doesn't call it again, but just gives us cached value. But there is something we can do about it, and it is the bound keyword:
var factor = 3;
var multiplier = 3;
def dst = bind getValue(multiplier);
println(dst);
factor = 5;
println(dst);

bound function getValue(p:Integer) {
 p * factor;
}
And now it prints:
9
15
Just as expected. bound keyword makes JFX analyse the function and checks which variables can affect return value. In this case it can recoginze return value is dependent not only on input parameter, but also on factor variable, so when variable changes, JFX calls the function again even if the parameter doesn't change. Now consider a little different code:
var factor = 3;
var multiplier = 3;
def dst = bind getValue(multiplier);
println(dst);
Thread.currentThread().sleep(5);
multiplier = 5;
println(dst);
Thread.currentThread().sleep(5);
println(dst);

function getValue(p:Integer) {
 System.currentTimeMillis();
}
Note that function is not bound again. sleep(5) is there to make sure two calls won't happen in the same millisecond. So it can give us:
1243112189488
1243112189496
1243112189496
(of course you'll get different values if running it at different time) First is number of milliseconds, second is again proper number of milliseconds, because function parameter value has changed and JFX called function again, and then second value is repeated, because parameter value didn't change and function return value is taken from cache. But what happens when we'll use bound? Let's see:
var factor = 3;
var multiplier = 3;
def dst = bind getValue(multiplier);
println(dst);
Thread.currentThread().sleep(5);
multiplier = 5;
println(dst);
Thread.currentThread().sleep(5);
println(dst);

bound function getValue(p:Integer) {
 System.currentTimeMillis();
}
And we get:
1243112415228
1243112415228
1243112415228
What? All three values are the same? So adding bound even made things worse! How is it possible? Well, bound makes JFX not only can recognize which variables affect return values, but also which function parameters doesn't affect it. So now JFX is aware that p doesn't affect return value of the function and it calls the function only on the first time. Then it takes value from cache, regardless parameter has changed or not. Conclusion: Be careful when using functions for binding variables. Be extremely careful when using functions dealing with current time (or better just don't do it ;)).

7 comments:

Koziołek said...

Binding is one of the most powerful function in jfx. But when programmer don't understand how those it work then fail ;)

Remember that binding variables could not be animated and changed to normal variables:

var d = 8;
var e = bind d;
e = 9; // doesn't work, runtime fail

Leszek Gruchała said...
This comment has been removed by the author.
Leszek Gruchała said...

When I was at the beginning of the last example I thought: Ha! I know what you want to do!. Add 'bound' and everything should be fine. And.... WTF?!

I didn't check this, but it should work:
bound function getValue(p:Integer) {
p=p;
System.currentTimeMillis();
}
But this only changes the value only for two first results. Maybe you found out how to 'fix' it? ;-)

Unknown said...

Leszek, I'm afraid p = p; won't work. "bound" analysis the function, and is aware that p does not affect return value.

What would I do? I wouldn't use binding. I would just call System.currentTimeMillis() every time I need.

Leszek Gruchała said...

Maybe yes, because the value of p inside the function will be changed during the second invoke... or maybe just not ;-)

Unknown said...

I checked it. It doesn't work.

Leszek Gruchała said...

Brute. I've broken my world ;]