Chapter 8. Data Binding

The bind keyword associates the value of a target variable to the value of a remote variable.

let x = bind someExpression; 

This binds the outcome of someExpression to the variable x. When someExpression changes, the value of x is automatically updated. The remote variable can be a simple value of some basic type, the outcome of an expression, the outcome of a block of expressions or a bound function. (The let keyword introduces a variable that cannot be assigned to).

Update Recalculations

In some cases you might need to know exactly how the update occurs, or what is meant by someExpression changing. When a remote value changes, a minimal recalculation is always performed. This recalculation only matters in limited circumstances. An example, is when a bind that requires object creation is performed and if, because of object identity, it matters whether a new object was created.

let sum = bind expr1 + expr2;

If expr2 changes sum is recalculated but expr1 is not. The value for expr1 was previously stored and is fetched again.

The following demo provides an example:

import java.lang.System;

var y = 3;
function ten() : Integer { 10 }
let sum = bind ten() + y;
System.out.println(sum);
y = 7;
System.out.println(sum);

The output of this program is as follows:

13
17

When y is set to 7, the function ten() is not invoked again because it did not change; the value has been remembered and reused.

Binding and Block Expressions

Within a bind, the only statements that can occur in a block expression are variable declarations. Note that assignment (including increment and decrement) are prohibited within bind. Thus a bound block expression has the following form:

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

Because any changes to the bound expression cause an update, and because that update is minimal, the variables are effectively bound. Note also that while, insert and delete are expressions and therefore cannot occur within a bind.

Binding and Conditional Expressions

Consider the following expression:

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

A change to condExpr changes which branch of the if expression is to be evaluated. A change to expr1 or expr2 does not cause either of the other expressions to be recalculated.

Binding and for Expressions

Consider the following expression:

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

If seq changes, the elements in newSeq that corresponded to elements still in seq are not recalculated. In other words; if an element is inserted into seq, the results of applying expr to that element are inserted into newSeq at the corresponding positions and the other elements are not recalculated.

The exception to this is that if expr uses indexof elem then those elements whose index changed will need to be updated, but corresponding to the minimal update rules.

import java.lang.System;

var min = 0;
var max = 3;
function square(x : Integer) : Integer { x*x }
let values = bind for (x in [min..max]) square(x);
System.out.println(values);
max = 5;
System.out.println(values);
min = 1;
System.out.println(values);
min = 0;
System.out.println(values);

In this case the following recalculations are performed:

  • Calculate the squares of 0 through 3. The squares of 4 and 5 (0 through 3 are not recalculated when the max changes.)
  • Delete the square of 0 without recalculating any values.
  • Add back the square of 0. This requires it to be recalculated. This behavior is the same if insert and delete are used instead.

Binding and Object Literals

Object literals behave like operators and nonbound functions. If one of the arguments to the object literal changes, it is re-executed (a new instance is created.)

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

If myX changes, the JavaFX™ Script programming language creates a new Point object -- the expected behavior for immutable objects.

To make the value of x track the value of myX without creating a new Point, binding is required:

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

For this example, you would probably want to bind y as well:

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

where pt would always remain the same Point instance. It would be the same without the initial bind:

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

Binding and Functions

A nonbound function is one that is not preceded with the bound keyword. For invocations of Java programming language methods or nonbound JavaFX Script programming language functions, the JavaFX Script programming language invokes the function again if any argument changes, but the body of a function is a black box. Dependencies it might have beyond its input parameters do not cause a recalculation.

import java.lang.System;

class Point {
     attribute x : Number;
     attribute 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;
let pt = bind makePoint(myX, myY);
System.out.println(pt.x);
myX = 10.0;
System.out.println(pt.x);
scale = 2.0;
System.out.println(pt.x);

Output:

3.0
10.0
10.0

Changing the argument myX causes makePoint to be called again. But, the function makePoint is a black box. Changing the value assigned to scale does not cause an update, as it probably should. The intent of a bound function: It causes the update that you would expect.

Bound functions have as their body a block expression that is bound (it thus has the previous restrictions on bound block expressions). When binding to a bound function occurs, changes besides the arguments cause an update, and the function sees changes to its arguments. The previous makePoint might have been made a bound function:

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

If so, the change to the value assigned to scale would now cause an update (20.0). Note that if myX changes, only x0 * scale would be recalculated, not y0 * scale .

Invoking a bound function from outside a bind is just like invoking a nonbound function.