6.4 Semantics of TeleoR Procedures

When a TeleoR procedure is executed, the guards are checked in order until a guard is found that is true (with a given instantiation of variables). The corresponding TeleoR Action is then executed. This action may be a TeleoR procedure and in which case its rules are checked in the same way. We say that a chosen rule is fired.

As with actions, if there are no matching guards, an exception is thrown.

Semantically, each TeleoR procedure is continually being checked to see which rule to choose. In practice, we only check when either the belief store has changed or a timeout has occurred. The timeouts of interest are those produced by a rule with a min_time being fired or because the rules action is a timed sequence.

We can think of a min_time to be the same as a test that is true if Duration time has not yet expired since the rule was first fired.

If, on rechecking, the same rule with the same instantiation of variables is chosen we say the rule is continued. If the same rule but with a different instantiation of variables is chosen we say that the rule is refired.

In the case where the fired rule contains an or_while then this rule will be continued if either Guard or Or remains true (with the same instantiation of variables) unless an earlier rule is fired.

In the case where the fired rule contains a commit_while then this rule will be continued if Commit remains true (with the same instantiation of variables) even if an earlier guard becomes true.

When a timed sequence like [turn(left):5, move(4):7] is called, the first action is called and then after that duration is up, the second action is called and so on until the last action in the sequence is called. After its duration has expired then the sequence is repeated from the start. This repetitive action continues whilst the rule with the timed sequence remains a fired rule. If the last duration is missing, that action persists whilst the rule with the timed sequence remains a fired rule. The sequence is execututed just once.

6.4.1 Example TeleoR program

As an example of TeleoR Programs, consider the following TeleoR program (from the examples/introduction directory of the release) controlling a robot whose objective is to find, approach, and pick up an object using grippers.


def dir ::= left | right | centre | dead_centre

percept see(num, dir), holding()

def primitive_action ::= move(num) | turn(dir) | grab() | release()     

%% We interpret holding true and see(0, centre) not true to mean that
%% the grippers are closed but not actually holding an object

tel get_object()
get_object() {
    holding() & see(0, dead_centre)       
            ~> ()
    holding() & see(0, centre)       
            ~> ()
    not holding()  & see(0, dead_centre)  
            ~> grab()
    not holding()  & see(0, centre)  
            ~> grab()
    not holding()                    
            ~> get_to()
    true                           
        ~> release() 
    }

fun op_dir(dir) -> dir
op_dir(left) -> right
op_dir(right) -> left
op_dir(_) -> centre

rel not_dead_centre_yet(dir)
not_dead_centre_yet(Dir) <=
    see(_, D) & D \= dead_centre & D \= op_dir(Dir)


tel get_to()
get_to() {
    see(0, dead_centre)  ~> ()
    see(0, centre)       ~> ()
    see(0, Dir)          ~> turn(Dir)
    see(_, dead_centre)  ~> move(6)
    see(_, centre)       ~> move(6)
    see(_, Dir) commit_while not_dead_centre_yet(Dir)
                         ~> move(4) , turn(Dir)
    true                 ~> [turn(left):5, move(4):7]
    }

Consider an initial state where no objects are seen and holding is false. When get_object is executed then the fifth rule is fired causing get_to to be executed. The last of rules of get_to will be chosen (a timed sequence). This will first cause the robot to start turning for 5 seconds and then start moving for 7 seconds. This will be repeated until an object is spotted.

At some point, say see(10, left) becomes true. This causes the sixth rule of get_to to fire (with Dir instantiated to 10). Assuming this object is not moved by the environment, then eventually, say see(8, centre) becomes true. It might seem that the fifth rule should now fire because its guard becomes true. However, the commit_while condition prevents higher rules from firing. Once, say, see(8, dead_centre) becomes true then rule four will fire. By over-achieving the guard of the sixth rule the "fluttering" between the fifth and sixth rule (without the commit_while condition) is eliminated.

Note that we have to be careful when using a commit_while in a rule as it causes this rule to take over. What happens if we simply use not see(_, dead_centre) as the commit_while test? If this test eventually becomes true then we don’t have a problem but what if the environment removes the bottle or moves it from left to right? In this case the test is still true but we shouldn’t continue with this rule. By using the more complicated test not_dead_centre_yet the test fails for one of three reasons: the bottle is no longer seen, the bottle is dead centre, or the direction has changed. We want to stop commiting to this rule when any of these conditions become true.

For example, if, before see(8, dead_centre) becomes true, the environment moves the object so that see(8, right) becomes true then there would be a refiring of rule six and the robot will start turning to the right.

Note that, if before the object is seen dead centre, see(0, centre) becomes true then rule four of get_object will fire. The commit only has a local effect - affecting rule choices within its own TeleoR program.

Eventually, without interference from the environment, either see(0, dead_centre) or see(0, centre) will become true. The third or fourth rule of get_object will now fire (stopping the execution of get_to), causing the robot to grip the object. Under normal circumstances holding will become true and then the first or second rule will fire causing the robot to stop.

It may seem that the robot’s job is done now that it has achieved its goal. However, the TeleoR program is still monitoring the state and say the environment now removes the object from the robot’s grip. Rule six will fire, opening the grippers, and then, once holding is no longer true, rule five will fire and the robot will go back to searching for an object.

6.4.2 Reactive TeleoR Rules

In many situations, like the two robot bottle collectors in examples/bottle_collector, the robot has both a goal directed part (e.g. collecting and delivering bottles) and a reactive part (e.g. avoiding other robots). It is clear that the reactive part should dominate over the goal directed part and probably the simplest way to implement this within TeleoR procedures is to add, in the top-level TeleoR procedure, reactive rules before goal directed rules so that the guards of the reactive rules will be checked before the guards of the goal directed rules.

In order to highlight this to the reader of the program we can surround the reactive part with chevrons as below.

communicating_collect_bottles(Total,OthrAg){ 
    >>>
        near(robot, _) or_while min_time 4 ~> avoid_robot(OthrAg)       
    <<<
    $collected + $other_collected >= Total ~> ()
    %% ....

The guard of the rule below <<< is the overall goal of the TeleoR procedure and it comes immediately after the reactive rule(s). In this case, if another robot is detected too close, the guard of the first rule becomes true and the TeleoR procedure will react by attempting to avoid the other robot. Once the other robot is no longer near, the TeleoR procedure will get back to its goal directed behaviour.

The chevrons are purely syntactic and have no effect on the semantics - if the chevrons were removed the procedure would behave exactly the same.


On This Site