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 to which it is attached. The syntactic nonterminals oldValue, lowIndex, highIndex, and newElements are effectively formal parameters to a function with a body that consists of 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 allowed only if the attached variable has sequence type.

Notice the 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. For example:

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

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

Unified replace triggers work well with slice assignments. For example, you can define a bind, as in the following:

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 the following:

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, might be decomposed to multiple trigger call. In that case the state that is seen by each trigger invocation is consistent. Specifically, the programmer-visible state is as if the predicate-delete (for example) were implemented as a set of independent slice-delete operations.