Loops

List Iteration

To do something for all members of a list, we have two main approaches: the classic recursion and the for-each.

Recursive approach

+!iterate([]).            // list is empty -> nothing to do
+!iterate([Head|Tail])    // does something with the head of the list
   <- dosomethingwith(Head);
      !iterate(Tail).     // and continues with the tail

Example:

!print([b,a,n,a,n,a]).

+!print([]).
+!print([Head|Tail])
   <- .print(Head);
      !print(Tail).

Prints:

b
a
n
a
n
a

For-each approach

+!iterate(List)
   <- for (.member(M,List)) {   // use .member as the query
         dosomethingwith(Head);
      }.
The Jason for is not like a typical for-each (witch iterates over a collection of values). Jason for iterates over a query, it can be read as "for each solution of a query".

Example:

!print([b,a,n,a,n,a]).

+!print(List)
   <- for (.member(M,List) & M \== a) { // note the use of a query as the argumet
         .print(M);
      }.

Prints:

b
n
n

For i in a .. b

The classical C like iteration with a counter variable can be implemented using recursion or for. Below the internal action .range is used as the for query to mimmic the typical C loop.

+!loop
   <- for (.range(I,A,B)) { // for i from a to b (included)
         dosomething(I);
      }.

Example:

!loop.

+!loop
   <- for (.range(I,1,5) & .range(J,1,5) & I > J) {
         .print(I," ",J);
      }.

Prints:

2 1
3 1
3 2
4 1
4 2
4 3
5 1
5 2
5 3
5 4

Query iteration

To do something for all solutions of a query we can (1) use .findall to place them all in a list and then iterate over the list or (2) use for loops. The pattern based on for follows.

+!loop
   <- for ( query ) { // for all solutions of `query`
         dosomething;
      }.

The example that follows prints all values of b that are greater than some p.

!loop.

b(10).
b(20).
p(05).
p(15).
p(25).

+!loop
   <- for (b(X) & p(Y) & X > Y) {
         .print(X);
      }.

Prints:

10
20
20
There are two 20 printed out since the query has 3 answers: { X=10, Y=5 }; { X=20, Y=5 }; and { X=20,Y=15}.

Plans execution interleaving

Regarding concurrent execution in Jason, we recommend the reading of this doc.

Singleton Plan

Some times we want that a plan has only one intention running it. For instance, when the agent wants to control the movement of a robot, it will be chaotic if two intentions with different targets are controlling the robot.

The following code avoids two or more concurrent executions of plan g:

+!g : not .intend(g) <- dosomething.
+!g. // do nothing for g

If the agent has two events +!g, when the first is handled, the agent does not intend g and the first plan is selected and thus the agent now intends g avoiding this plan to be selected again.

This pattern considers g as achieved if some other intention is trying it (cf. the second plan for g).

Example:

e.
+!e
   <- for ( .range(I,0,5) ) { // creates 6 concurrent intentions for g
         !!g(I);
      }.

+!g(I) : not .intend(g(_))
   <- .wait(200);
      .print(I).
+!g(I).

Prints:

0

"Queued" Singleton Plan

As for the singleton plan, we want that only one intention is executing the plan. However, when an intention has no access to the plan, it waits for the running intention to complete and then tries again to execute.

+!g : not .intend(g)   // I do not intend g yet, so starts doing something to achieve g
   <- dosomething;
      !!resume(g).     // resume other intentions waiting to execute
                       // must be done by a new intention (!!) and be the last command in the plan,
                       // otherwise this plan will not be selected again

+!g                    // a plan for g is running, suspends this tentative
   <- .suspend;        // (when the running intention finishes, it will resume this one)
      !!g.             // tries again (note that we have to use !!, otherwise this intention prevents the above plan to be selected)

+!resume(G)
   <- .resume(G).      // resume all suspended Gs.
The annotation atomic could be considered for the first plan to achieve g (the plan being @[atomic] +!g ← dosomething.). However it may constraint too much the agent execution: no other intention (even not related to g) will run until +!g is finished. The reactivity of the agent can be compromised, specially in cases where dosomething takes a lot of time to execute. If dosomething is fast, atomic can be considered since it is far simple to use than this patter (this pattern adds two extra plans).

Example:

!e.
+!e
   <- for ( .range(I,0,5)) { // creates 6 concurrent intentions for g
         !!g(I);
      }.

+!g(I) : not .intend(g(_))
   <- .print(I);
      .wait(50);
      .print(I);
      !!resume(g(_)).
+!g(I)
   <- .suspend;
      !!g(I).
+!resume(G)
   <- .resume(G).

Prints:

0
0
4
4
5
5
2
2
3
3
1
1
There is no interleaving among the execution of the intentions.

This pattern is used in the Santa Claus example that comes with Jason.

Plan dependency (synchronisation)

Suppose you have two plans:

+!a <- action1.
+!b <- action2.

and you need that the second plan is executed only when the first is already finished. However, you are not able to define the order of events +!a and +!b (their are externally determined).

Procedural goals

An initial tentative is:

+!a <- action1; .resume(b).
+!b <- .suspend; action2.

the second plan suspends itself and the first resumes the second when finished. It works often, but some very particular interleaving executions can break it: when the .resume(b) is executed and the second plan hasn’t executed .suspend yet. The following example creates this situation (.wait is used to force the delay in the execution of .suspend):

!a.
!b.

+!a <- .print(a); .resume(b).
+!b <- .wait(1000); .suspend; .print(b).

The first plan should wait for the second to be suspended by the internal action .suspend before resuming it:

+!a <- action1; .wait( .suspended(b,suspend_ia) ); .resume(b).
+!b <- .suspend; action2.

If by some reason you do not want to change the two intitial plans, meta-events can be used:

+!a <- action1.
+!b <- action2.

^!b[state(pending)]  <- .suspend(b). // suspend b when starting
^!a[state(finished)] <- .resume(b).  // resume b when a finishes

Declarative goals

If the first plan is for a declarative goal (i.e., the goal !a refers to a fact that the agent initially believes a is not true, and if the goal is achieved, the agent will believe that a has become true), another solution is possible: the second plan can simply wait for the agent to believe in a.

+!a <- action1.
+!b <- .wait(a); action2.

In this case, of course, we should ensure that action1 will change the environment so that the agent will perceive a later.

Indeed we can relax a bit this pattern, and replace the a in .wait(a) by any belief we know that will hold after the first plan execution.

Commitment

The section 8.3 of the Jason Book presents some useful patterns to configure the agent commitment towards some goal. Two of these patterns are included here.

Single-Minded Commitment

In this pattern we want an agent that keeps trying to achieve goal g until it believes g is achieved (as a declarative goal) or that g is impossible. See paper "Cohen & Levesque. Intention is choice with commitment. Artificial Intelligence 42(3), 213–261, 1990."

Supposing that to believe in f implies that g is impossible, the pattern can be written as:

+!g : g.                // if I already believe g, there is nothing to do
+!g : .intend(g).       // if there is an intention to g already, do nothing

+!g : somecontext1      // a possible plan to achieve g
   <- dosomething1;
   ?g.
+!g : somecontext2      // another possibility
   <- dosomething2;
   ?g.

+!g : !g.               // no applicable plan, keeps trying
-!g <- !g.              // if the above plans have failed, keeps trying hopping for better conditions
+g <- .succeed_goal(g). // stops trying g if g
+f <- .fail_goal(g).    // stops trying g if f
The plans to achieve g end with ?g, so they only succeed if after doing something to acheive g the agent believes g (for instance, it perceives g).

In Jason, a directive is available to simplify the use of this pattern:

{ begin smc(g,f) }
+!g : somecontext1 <- dosomething1.
+!g : somecontext2 <- dosomething2.
{ end }

Maintenance Goal

In the case of a maintenance goal, the agent should keep g always true. Whenever it realises that g is no longer in its belief base, it attempts to bring about g again by having the respective (declarative) achievement goal.

-g <- !g.

// the code to achieve g follows

{ begin bc(g) }
+!g : somecontext1 <- dosomething1.
+!g : somecontext2 <- dosomething2.
{ end }
BC (Blindly Commitment) is a pattern similar to SMC, without the failure condition f.