Performing an action at the end of a group (or any other compound action) may be difficult if the delays of the group’s actions are expressions or if some conditional constructs are involved. Even with constant delay and no control structure, computing the duration of a group, or a loop/whenever/forall body, can be cumbersome.

This observation advocates for the introduction of two additional sequencing operators that are used to launch an action at the end of the preceding one:

(no special name) followed-by     ended-by
a b a ==> b a +=> b
b is launched together with a b is launched at the end of a b is launched at the end of a and its children

The juxtaposition (launch synchronously/in parallel), the followed-by operator (launch at the end) and the ended-by operator (launch at child ends) are binary operators called continuation combinators. Delays are not continuations1: they are unary operators applied to an action to defer its start.

The effects of the three operators are illustrated by the figure below. The end of a compound action is represented by the bold outlined rectangle. The child actions may end earlier or later than the end of the top-level actions:

  • The juxtaposition aligns the starts of two sequences;

  • the ==> "followed-by" opeartor align the end of the first sequence with the start of the second;

  • and the +=> "ended-by" aligns the latest end (the final launching of an action, including a child action) with the start of the second sequence:

illustration of the continuation combinators

Continuation combinators do not change the scope of the local variables of their arguments. In other words, in a ==> b the actions in b cannot access the local variables defined in a.

Continuation combinators freely compose between actions and are right associative. Here are some examples:

Expression Meaning
a ==> b ==> c is equivalent to a ==> { b ==> c } and specifies that b ==> c starts at the end of a
{ a ==> b } ==> c starts c at the end of { a ==> b}, that is, with the end of b
{ a ==> b } c starts c with the start of { a ==> b }, that is, with the start of:::antescofo a`
a ==> b c is equivalent to a ==> { b c } and starts { b c } with the end of a
a b ==> c is equivalent to { a b } ==> c and starts c with the end of { a b }, that is, the end of b
a +=> b ==> c is equivalent to a +=> { b ==> c } and starts { b ==> c } at the end of ‟a and its children”

For instance, suppose we want to make an action after the end of a loop:

          $cpt := 0
          Loop 1 
                print "tic" $cpt
              3 print "tac" $cpt
                $cpt := $cpt + 1
          } during [3#]
          +=> print "loop ended"

Here there will be 3 iterations of the loop. So, if the loop starts at date 0, the first iteration starts at 0 and ends at 3, the second one starts at 1 and ends at 4 and the last one starts at 2 and ends at 5.

Instead of explicitly computing these numbers to launch an action at the right time, we have used the continuation combinator +=> which waits the end of the loop and all the loop bodies, to trigger the print message: the message "loop ended" will appear at date 5.

As you can see, the end of a loop is different from the ends of the loop bodies: the loop in itself terminates when the last iteration is launched. As a matter of fact, the computation associated to a compound action a can be seen as a tree, with the sub-computations rooted at a. Thus, there is no need to maintain a after having launched the last sub-computation. So the end of a is usually not the same as the end of the last sub-computation spanned by a and this is why the operator is usually more handy than ==>.

Nevertheless, the end of an action is always precisely defined altough it can be only dynamically known:

  • atomic action: the start and the end of the action coincide.

  • coumpound actions without duration: are actions that launch other actions but they do not have a duration by themselves because they do not need to persist in time. Such actions are the if, switch, and the forall. The start and the end of these actions coincide. However, these actions have children: the actions launched by these constructs.

  • compound actions with a duration: the start and the end of these actions usually differ:

    • Group G { a ... b }: the start of G coincides with the start of a. The end of G coincides with the start of b (the last action in the group). The children of G are all actions launched directly (they appear explicitly in the group body) or indirectly (they are launched by a child of G).

    • Loop L { a }: the start of L coincides with the start of the first iteration of a. The end L of coincides with the last iteration of a. The children of L are the actions launched in the loop bodies.

    • Whenever W { ... }: there is no relationship between the start of W and the actions in it body. Usually, there is no end to a whenever except if there is a during or an until clause. In this case, the whenever terminates when the clause becomes true. The children of W are the actions launched by the instantiations of the whenever body.

    • A process call or an object instantiation: their end coincides with the end of the associated instance. An object has no end per se and must be aborted.

Continuation and abort

Abort handlers launch a group at the premature end of a compound actions. So they differ from the followed-by and ended-by operators that launches a group of actions at the (natural or premature) end of an action.

An abort handler, specified by the @abort attribute, is considered as a child of the associated action. So, when an abort handler exists and the associated action is aborted, the abort handler is launched with the followed-by continuation (if it exists). Because the abort handler is necessarily defined before, it happens before the followed-by continuation.

The ended-by continuation is launched after the end of the abort handler (because the abort handler is a child).

Continuations are not considered children of the continued action. So in a ==> b, b do not has access to the local variables of a, contrary to the @abort clause of a.

For example (note the bracketing of the process call):

          @proc_def ::P()
          @abort { print abort P $NOW }
               print start P $NOW
            10 print BAD END P $NOW

          { ::P() ==> print continuation P $NOW }

          print "launch abort" $NOW
          abort ::P

will give the following trace:

      start P 0.0
      launch abort 5.0
      abort P 5.0
      continuation P 5.0

The continuation is launched at date 5.0 because it is launched with the end of the instantiated process (here a premature end caused by the abort command). If the abort handler is replaced by:

          @abort { 11 print abort P $NOW }

the corresponding trace is:

      start P 0.0
      launch abort 5.0
      continuation P 5.0
      abort P 16.0

because the followed-by continuation does not wait the end of the abort handler. If we replace the followed-by continuation by an ended-by continuation

          { ::P() +=> print continuation P $NOW }

the trace becomes:

      start P 0.0
      launch abort 5.0
      abort P 16.0
      continuation P 16.0

because the continuation takes place at the end of all of the action's children, including the abort handler.

  1. Because a delay can be used in front of the first action of a sequence, a delay does not necessarily links two successive actions.