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 .
|