Chapter 9. Triggers

Replace Triggers

The syntax for a replace trigger is as follows:

 "on" "replace"
    [oldValue]
    [ "[" lowIndex ".." highIndex "]" "=" newElements ]
    block

A replace trigger is invoked after every modification of the variable it is attached to. The syntactic non-terminals oldValue, lowIndex, highIndex, and newElements are effectively formal parameters to a function whose body is the block:

  • oldValue is the previous value of the variable, and has the same type as the variable.
  • lowIndex and highIndex delimit the portion of oldValue that has been replaced. Their types are Integer. For a pure insertion, highIndex==lowIndex-1.
  • newElements is the sequence of values that replaces the slice oldValue[lowIndex..highIndex]. Its type is the same as the variable.

The [lowIndex..highIndex]=newElements parameters are only allowed if the attached variable has sequence type.

Notice that there is an asymmetry between oldValue and newElements: oldValue is the previous value of the attached variable, while newElements is a slice of the new value of the variable, containing only the modified elements.

The syntax of the replace trigger is meant to be evocative of a slice assignment. Given:

attribute x
    on replace oldVal[lo..hi]=newVals { exp };
  var save = x;
  x[i..j] = y;

Then exp will be evaluated, with oldVal bound to save, lo bound to i, hi bound to j, and newVals bound to y.

Unified replace triggers work great with slice assignments. For example, we can define a bind, as in:

attribute x;
attribute y = bind x;

as equivalent to:

 attribute x =
    on replace [i..j]=n
    { y[i..j]=n };
  attribute y = [];

If y is a map of x:

attribute x;
attribute y = bind for (xi in x) f(xi);

that is equivalent to:

  attribute x =
    on replace [i..j]=n
    { y[i..j] = for (k in n) f(k) };
  attribute y = [];

Deleting or replacing a range of elements in a sequence, or inserting a sequence into a single location all result in a single trigger invocation. Some other operations, such as deleting all elements that satisfy a predicate, may be decomposed to multiple trigger call. In that case the state as seen by each trigger invocation is consistent in the sense that the programmer-visible state is as if the predicate-delete (for example) were implemented as a set of independent slice-delete operations.