This section informally describes the concrete syntax of Qulog. There is a formal extended BNF syntax in the HTML file EBNF Grammar for Qulog and as an Appendx of this Manual.
The basic building block is an expression. An expression is a: data constant (aka atomic value), variable, compound term, list, set, code name, function call, list comprehension, set comprehension.
We define each of these categories below.
A reader unfamiliar with logic programming might find it odd that a variable is considered a data value. However in both QuLog and Prolog variables are first class values and can be passed between calls and embedded in lists and other compound terms, but not in sets. An answer to a query that contains a variable denotes the set of instantiates of that answer where the variable is replaced by any value of its type. The ability to pass around terms that are or which contain variables is a powerful programming feature of QuLog and Prolog. It is not a feature of Datalog or Answer Set Logic Programming.
The last three are evaluable expressions that denote a ground term.
A term is a: data constant, code name, a simple compound term (see below) all arguments of which are terms, a list of terms, a set of ground terms.
A ground term is a term containing no variables. QuLog function call evaluation is strict. A function call argument is completely evaluated just before the function call in which it appears is evaluated.
A code name is a value of system type code. For example append and *
Both term and code are sub-types of system type top
A ground expression is an expression that contains no variables, or is such that all its variables are bound to ground values at the point that the expression is evaluated.
QuLog strings are not lists of byte codes as in Prolog. They are packed sequences of byte codes as in Python an are stored on the heap and are garbage collected when no longer referenced. Identity of strings is determined by character by character matching if they have the same length. Manipulation of strings - concatenation and sub-string extraction involves copying but is quite fast.
As in Prolog, QuLog atoms are stored in an atom table and are replaced by a pointer to its entry in the atom table. Identity of atoms is then identity of atoms table address, there is no character by character matching at runtime. Atoms are a suitable alternative to strings for character sequence values that will not be manipulated and are not transient values. For example use them for names of things in facts. The atom table entries are not garbage collected.
All data contants are sub-types of the system type atomic.
nat is a sub-type of int, which is a sub-type of num
There are four syntactic forms for atoms.
newline | Meaning: Continuation. |
^ | Meaning: Same as d. |
^character | Meaning: Control character. |
dd | Meaning: A two digit octal number. |
a | Meaning: Alarm (ASCII = 7). |
b | Meaning: Backspace (ASCII = 8). |
c | Meaning: Continuation. |
d | Meaning: Delete (ASCII = 127). |
e | Meaning: Escape (ASCII = 27). |
f | Meaning: Formfeed (ASCII = 12). |
n | Meaning: Newline (ASCII = 10). |
odd | Meaning: A two digits octal number. |
r | Meaning: Return (ASCII = 13) |
s | Meaning: Space (ASCII = 32). |
t | Meaning: Horizontal tab (ASCII = 9). |
v | Meaning: Vertical tab (ASCII = 11). |
xdd | Meaning: A two digit hexadecimal number. |
Here are a few examples of quoted atoms.
'hi!'
'they''re'
'\n'
The available range of integers is -(2^31-1) to 2^31-1 on a 32 bit machine and -(2^63-1) to 2^63-1 on a 64 bit machine. Integers can be represented in any of the following ways.
gives the ASCII character code 65.
27.8
1.896e4
Any sequence of characters enclosed by '"' is considered as a string.
Note: Strings in Qulog are the same as Python strings and NOT Prolog strings.
Example:
| ?? "Hello"++"there".
"Hello there" : string
These were names we have to precede with a back-quote if we want to use them as atoms. Without a preceding back-quote they denote code values.
Variables are available in three syntactic forms.
Variables beginning with an underscore should be used when there is just a single occurence of a variable in a rule. It suppresses the "single occurrence of variable" warning which is given otherwise, which is useful for picking up mis-typed variable names.
A simple compound term is composed of an atom of the first two forms (an alphanumeric or graphic atom), called the functor, immediately followed (no spaces) by a sequence of zero or more expressions separated by commas, enclosed in a pair of "("..")" parenthesis. For example:
data(jack, 35)
tr(emp(),X/9,tr(L,7,R))
$$(5)
Simple compound terms are typically instances of a structured data type declared in the program where the functor is a constructor for the type. If not, the compound term has default type term, and a warning that it is not a constructor of a defined type is issued in case there has been a spelling error.
A compound term is a simple compound term, or a compound term immediately followed by a sequence of zero or more expressions separated by commas, enclosed in a pair of "("..")" parenthesis. For example:
curry(*)(4)
curryR(child_of)(mary)(P)
Compound terms that are not simple determine the functor of the compound term by a function call which is itself a compound term.
A function call is a either simple compound term where the functor is the name of a primitive or program defined function, or it is a non-simple compound term where the compound term that denotes the functor is a function call that returns a function value.
For certain binary primitive functions the functor name may be used as an infix operator and placed between the two arguments. This holds for the usual binary arithmetic operators +, * etc. for which function applications are written as expressions such as 6+9*X.
The special zero argument functions e_ and pi_, invoked as in expressions e_() and pi_(), evaluate to the numbers 'e' and 'pi'. More details are given in Section 3.4
In the QuLog interpreter a function call, indeed any expression, can be given as an entry to be evaluated.
Examples:
| ?? 67.7/2.3.
29.4348 : num
| ?? curryR(child_of)(peter).
curryR(child_of)(peter) : (atom)<=
% The denoted value is a relation over atom names
Function calls denote expressions that contain no function calls. That is they denote non-variable terms: atomic values, code names, simple compound terms all the arguments of which are non-variable terms, lists or sets of non-variable terms. The exceptions are certain code returning function calls which are only evaluated when the code value they denote is itself called. The above curryR(child_of)(peter) is an example. It denotes an unary relation but that relation is only used when the unary relation is called in a query such as:
| ?? P ? Rel=curryR(child_of)(peter) & Rel(P).
P = harry : atom
P = mary : atom
A list is a comma separated sequence of expressions enclosed in "[".."]" brackets. This is a complete list. Or it is a comma separated sequence of terms ending with ,.. optionally followed by a variable, or ending with |) always followed by a variable, enclosed in "[".."]" brackets.
Example:
[3,2.7,X*Y,"hello"]
[3,4,..Tail]
[Head,..Tail]
[Head,..] % shorthand for [Head,.. _] with _ the anonymous variable
[Head|Tail]
The first example is a complete enumeration and the remaining examples are list patterns in which both ,.. and | can be read as "followed by". The fourth example is equivalent to [Head,.. _]. The last example is using the Prolog syntax for a list.
Lists of ground terms can also be denoted by a list compehension expression. Examples are:
[X**2::X in L & not X in [0,1]]
% Squares of numbers other than 0,1 in nums list L
[C :: exists A child_of(C,peter) & age_of(C,A) & A<18]
% Non-adult children of peter in order found
The general form of a list comprehension is:
[Expression :: exists VarSequence SimpleConjunction]
where the exists VarSequence is optional.
There are constraints on the variables that can be used in such a comprehension. Each variable in Expression must either appear in SimpleConjunction and be such that it will be given a ground value by some call in the conjunction, or it must appear before the comprehension expression in a query or rule and will have been given a ground value. Every variable in SimpleConjunction must either be underscore, appear in Expression or in VarSequence, or must appear before the comprehension expression in a query or rule and will have been given a ground value. This ensures that the value of a list comprehension is always a list of ground terms.
VarSequence is a single variable or a comma separated sequence of variables such as X,Y,Z
The syntax for SimpleConjunction is given below.
A set is a comma separated sequence of ground expressions surrounded with { and } braces. If there are any duplcate ground terms when all the expressions have been evaluated all but one of the duplicates will be removed. If returned as the value of a expression entry to the interpreter, or as a binding of a variable in a relation query, it will be diplayed with its elements in term order as determined by the @ primitive.
Example set expression entry:
| ?? {4,3,1,5-2,1}.
{1, 3, 4} : {nat}
Sets can also be denoted by a set compehension expression. Examples are:
{X**2::X in L & not X in [0,1]
% Squares of numbers other than 0,1 in L, duplicates removed.
{A :: exists C, P child_of(C,P) & age_of(C,A) & A<18}
% Set of all the ages of recorded children
The general form of a set comprehension is:
{Expression :: exists VarSequence SimpleConjunction}
where the exists VarSequence is optional.
The constraints on the variables that can be used in a set comprehension are the same as those for a list comprehnesion expression.
VarSequence and SimpleConjunction are as for list comprehensions.
A QuLog/TeleoR program also includes:
TeleoR procedures.
They may appear in any order except that all the rules for a particular relation, action or function must be contiguous. A type declaration for a relation, action, function or procedure does not need to be given immediately before its code. The rules of a TeleoR procedure are all included inside {...} braces following the procedure head.
An important constraint is that each type definition, type declaration, relation, action and function rule must begin at the left end of a new line. If one needs to be continued over more than one line all but the first line must be indented from the left end by at least one space or tab. Fullstop terminators may be given at the end of each definition, declaration or rule but is not needed. It is the text starting at the extreme left end of a line after one or more newlines that terminates the previous program statement. TeleoR procedures must also begin at the left end of a line but inside the {...} there are more relaxed layout constraints. Each TeleoR rule can start anywhere on a new line. The parser can use the rule syntax to determine that it is the start of a new rule. Again fullstop terminators may be given at the end of each rule but are ignored.
Integer range type expression
This is an expression of the form M..N where M < N and both are integers.
Examples:
digit::= 0..9
small_int::= -10..10
As in the examples different range types may overlap but only when one is completely contained inside the other. To have overlapping sets of integers corresponding to different types, type union must be used (see below).
Disjunction of constants type expression
This is an expression of the form C1 | C2 | ... | Ck where each Ci is the same kind of constant, except that we can mix different types of numbers.
Examples:
gender::= male | female
threeNums::= 20 | 6.7 | -50
article::= "a" | "an" | "one" | "the" | "that" | "those"
Different type definitions using overlapping disjunctions of constants are allowed providing one is completely contained inside the other. So, as well as the article type we could define
indef_article::= "a" | "an" | "one"
A disjunction of integers can also overlap with a range type providing it either comprises a subset or a superset of the integers of the range type. These constraints ensure that each constant belongs to a unique minimal type. For example "a" would belong to the types indef_artcle, article, string, atomic, term, top.
To have partially overlapping disjunctions of constants corresponding to different types, type union expressions must be used to define each partially overlapping type (see below).
Parameterised type expression
This is an expression of the form CT1 | CT2 | ... | CTk where each CTi is a compound term with arguments that are type names, or a single type variable T, or a parameterised type name with argument the same type variable T. Such a type expression can only appear as the right hand side of a parameterised type definition with left hand side a unary compound term containing the type variable T.
Examples:
tree(T) ::= empty() | tr(tree(T),T,tree(T))
an_indexed(T)::= rec(int,T)
Type union expression
This is an expression of the form Ty1 || Ty2 || ... || Tyk where each Tyi a simple type name or a ground parameterised type name or a code type expression.
Examples:
int_atom ::= int || atom
int2intOrstring::= int -> int || int -> string
Code type expressions
The last example above was the union of two function type expressions. There are four code type expressions in Qulog/TeleoR. These are: a function type, a relation type, and action type and a TeleoR procedure type.
Function type expression
This has the form (TE1,TE2,...,TEk) -> TE where each TEi and TE is any simple, or compound type name, or type union expression, or a code type expression.
Relation type expression
This has the form (MTE1,MTE2,...,MTEk) <= where each MTEi is a moded type where the type is any simple, or compound type name, or type union expression, or a code type expression.
The possible modes of a moded type are the prefixes !, ? and ?? and the postfix ?.
The moded type !Type used as an argument of a relation means, when called, the supplied argument must be ground and of type Type.
The moded type ?Type used as an argument of a relation means, when called, the supplied argument must either be ground and of type Type or will be ground to a term of type Type by the call.
The moded type Type? used as an argument of a relation means, when called, the supplied argument must either be ground and of type Type or, if ground by the call, will be ground to a term of type Type.
The moded type ??Type used as an argument of a relation means, when called, the supplied argument, if ground, must be of type Type and the call will not further instantiate the argument.
Modes can be used multiple times in structured types as long as inner modes are more liberal than outer modes. For example, the moded type
![?int]
means that the top-level list structure must be given (i.e. the number or elements are known at call time) but the elements of the list can be a mixture of
integers and variables with the variables instantiated to integers by the call.
Action type expression
This has the form (MTE1,MTE2,...,MTEk) ~>> where each MTEi is a moded type where the type is any simple, or compound type name, or type union expression, or a code type expression.
The modes are as described above. TeleoR type expression This has the form (TE1,TE2,...,TEk) ~> where each TEi is any simple, or compound type name, or type union expression, or a code type expression.
Name : Type
Examples of declarations are given below where definitions are described.
If multiple functions, relations, actions or teleoR programs have the same type their names can all be listed on the left hand side of a declaration as follows.
Name1, ..., NameN : Type
As an example, below is the type declaration for the builtin append relation (with the same semantics as the standard Prolog append relation).
append : (![T], ![T], ?[T]) <= |
(?[T], ?[T], ![T]) <= |
(![T?], ![T?], ?[T?]) <= |
(?[T?], ?[T?], ![T?]) <= |
([T]?, [T]?, [T]?) <=
The first type of append says that, if the first two arguments of the call on append are ground lists of a given type, then the third argument will be a ground list of the same type on exit from the call.
The second type says that, if the third argument is a ground list of a given type, then the first and second arguments will be ground lists of the same type on exit from the call.
The third type of append says that, if the first two arguments are lists of a known length (i.e. do not have a variable tail) but possibly containing non-ground elements, then the third argument will have a known length on exit from the call but that variables occurring in any of the arguments need not be ground.
The fourth type is the "append driven backwards" version of the third type.
The fourth type is the most general allowing variable length lists in all arguments. In this situation, nothing can be said about the modes on exit from the call.
Note that when we say, for example, the first two arguments are of the same type we mean that the type inference system can find a suitable type as in the example interpreter query below.
| ?? append([1,2], [a,b], X).
X = [1, 2, a, b] : [atom || nat]
Here, the suitable (minimal) type for T is the union of two types.
Beliefs are dymanic relations and are declared as follows.
belief Name1 : ArgTypes1 , ..., NameN : ArgTypesN
Where each ArgTypesI is of the form
(TE1,TE2,...,TEk) in which each TEi is any simple, or compound type name, or type union expression, or a code type expression.
The declaration
belief age_of: (human,age)
is esentially the declaration
age_of : (?human, ?age) <=
together with the declaration that age_of is dynamic.
Percepts are similar to beliefs in that they are dynamic but are specifically used for storing percepts in teleoR programs. They are declared as follows.
percept Name1 : ArgTypes1 , ..., NameN : ArgTypesN
Global variables are used to store either integer or number values and are declared as follows.
int Name := IntValue
or
num Name := NumValue
The declaration
int count := 0
is like a combination of the declaration
belief count : (int)
and the definition
count(0)
with the restriction that the count belief always contains exactly
one fact.
Predications
These are atoms that are names of no argument defined relations or compound terms with functors that are the names of primitive or program defined relations with argument types consistent with the relation's declared type. A compound term with a functor that is an expression of relation type consistent with the argument types of the compound term is also a predication, this includes the case where the functor is a variable. The compiler will also check that all varibles of any argument term of the predication that must be ground will have have been given ground values by the time the predication needs to be evaluated.
Negated Predications
These have the form not Cond where Cond is a predication. The compiler will check that any arguments of the predication Cond that need to be ground will have ground bindings for all their variables before the not is evaluated. All other arguments must also be ground values by the time the condition is evaluated, or they must be anonymous underscore variables.
not Cond is deemed to have been inferred if, and only if, a complete search of the tree of alternatve possible inferences of Cond fails to find a proof. It is the so-called negation-as-failure. It is a sound rule of inference on certain assumptions regarding the completeness of the relation definitions used in the exploration of the possible proofs of Cond and on the assumption that different data terms (after being normalised in the case os sets) denote different values.
Meta-calls
These have the form call Var where Var is of type relcall. relcall is the system type comprising all terms that denote type correct calls to primitive or program defined relations. The meta call call Var succeeds providing the relation call denoted by the relcall value of Var at the time of evaluation has all its input arguments ground and will succeed.
Complex Conditions
Negation. If Predication is inferable then fail else succeed. At the time of call all variables appearing in Goal must be ground or underscore variables.
mode/type not : (??relcall) <=relcall is the system type comprising all terms that are type correct calls to primitive or program defined relations. The functor of the predication must either be given or be a variable with known moded type so that the compiler can check that the call is type correct and that any arguments that need to be ground will have ground bindings before the not is evaluated. The only variables that need not already have ground bindings are underscore variables. None of these can be in ! argument positions of the called relation.
Find first solution to SimpleConj and discard any alternative choices. If SimpleConj is just a predication the brackets are not needed.once cannot be used inside a SimpleConj. It may only be used in an interpreter query or in the conjunctive body of a relation rule.
call Call
mode/type call : (relcall?) <=relcall is the system type comprising all terms that are type correct calls to primitive or program defined relations. The functor of the predication must either be given or be a variable with known moded type so that the compiler can check that the call is type correct and that any arguments that need to be ground will have ground bindings before the call is evaluated.
forall UVars (SimpleConj1 => exists EVars SimpleConj2)
If, for all bindings of variables in UVars that make SimpleConj1 inferable, exists EVars SimpleConj2) is also inferable, then the forall succeeds, otherwise it fails.UVars must be a collection of new variables and all variables occurring in SimpleConj1 must be in UVars, have a ground value before the forall is evaluated, or be an underscore variable.
EVars must also be a collection of new variables and all variables occurring in SimpleConj2 must be in UVars or EVars, have a ground value before the forall is evaluated, or be an underscore variable. exists EVars is optional.
forall cannot be used inside a SimpleConj. It may only be used in an interpreter query or in the conjunctive body of a relation rule.
Assuming we have a collection of child_of(C, P) beliefs that associate a child C with a parent P, and a collection of person(Name, Gender, Age) beliefs, giving the gender and age of people. The following query will return as an answer each parent who has at least one adult child.
child_of(_, P) &
forall C (child_of(C,P) => exists A person(C,_,A) & A>20)Note that P will be given a value before the call on forall and so there are no free variables in the body of forall other than C and the underscore variable.
Also note that each quantified variable is not allowed to appear outside the quantification or in other quantifications.
Head :: Commit <= Body
where Head is an atom or simple compound term, Commit is a conjunct of goals, and Body is a conjunct of goals. Conjuncts are separated by &. Both the :: Commit and <= Body parts of the rule are optional. The heads of each rule of a relation have the same functor and arity.
When a goal Goal with the same functor and arity as Head is called, the rules of the relation are tried in order. If Goal unifies with the head of the rule then the Commit part is called. If that succeeds then this rule is committed to (i.e. no subsequent rules are tried on backtracking) and Goal succeeds if and only if Body succeeds. If Body is not present it is treated as being the goal true.
If Commit is not present then Goal succeeds if Body succeeds but, on backtracking, subsequent rules will be tried.
The rule has the same semantics as the Prolog rule
Head :- Commit, !, Body
Note, however, that cut (!) is not part of Qulog.
As examples, the definitions of the relations greater and sum_list are given below.
greater: (!num, !num, ?num) <=
greater(A, B, C) :: A > B <= C = A
greater(A, B, C) :: B > A <= C = B
sum_list : (![num], ?num) <=
sum_list([], 0)
sum_list([H,..T], N) <= sum_list(T, M) & N = H+M
Note that in N = H+M, H+M is evaluated before unification and that the second rule of greater could have been written as
greater(A, B, C) <= B > A & C = B
Head :: Commit ~>> Body
where Head is an atom or simple compound term, Commit is a conjunct of goals, and Body is a sequence of goals and actions. The elements of the sequence are separated by ;. Both the :: Commit and <= Body parts of the rule are optional. The heads of each rule of an action have the same functor and arity. The semantics of action definitions is the same as for relation definitions. The difference is that at least one of the elements of the Body sequence is an action which typically has a side effect such as writing, reading, sending a message or updating the database. As examples, the definitions of the actions ask_query and handle_response are given below.
ask_query: (atom, term?, [term ?], handle)
ask_query(QId,Ans,QList,Ag) ~>>
ask(QId,Ans,QList) to Ag; Reply from Ag;
handle_response(QId,Ag,Reply,Ans)
handle_response: (atom,handle, term?, term?)
handle_response(QId,_,tell(QId,ans(Ans)),Ans) :: true
handle_response(QId,Ag,tell(QId,Reply),_) ~>>
writeLine(['Agent ',Ag,' responded ',Reply,' to query ',QId]); fail
Head :: Commit -> Expression
where Head is an atom or simple compound term, Commit is a conjunct of goals, and Expression is a term. The :: Commit part of the rule is optional. The heads of each rule of a relation have the same functor and arity.
When the function is called, the rules are tried and the first rule whose Head unifies with the function call and where Commit is true then the result returned is the evaluation of Expression. Note that rules without an explicit Commit have an implicit true commit and so no backtracking occurs.
As examples, the definitions of the functions factorial and tree2list are given below (using the tree type given above).
factorial : nat->nat
factorial(0)->1
factorial(N) :: N1 = N-1 & type(N1,nat) -> N*factorial(N1)
tree2list : tree(T) -> [T]
tree2list(empty()) -> []
tree2list(tr(LT, V, RT)) -> tree2list(LT) <> [V] <> tree2list(RT)
Note that type(N1,nat) is necessary above in order that the type checker can type check the recursive call on factorial.
Head {
TR Rule
...
TR Rule
}
where Head is an atom or simple compound term and each TR Rule has one of the forms given below.
The most simple TR rule has the form
Guard ~> TR Action
where Guard is a conjunct of goals and TR Action is of the form given below.
At the other extreme, the most complete form of a TR rule is
Guard while While min Duration until Until min Duration ~> TR Action
where While and Until are conjuncts of goals and Duration is a number.
The most simple rule above is a particular form of the full rule where both While and Until are false and both durations are 0.
Other variations of the general form are:
Guard min Duration ~> TR Action
which is the same as
Guard while false min Duration until false min Duration ~> TR Action
Guard while_until Goal min Duration ~> TR Action
which is the same as
Guard while not Goal min Duration until Goal min Duration ~> TR Action
Guard while_until Goal ~> TR Action
which is the same as
Guard while not Goal min 0 until Goal min 0 ~> TR Action
Semantically, when a TR program is executed, the guards are checked in order until a guard is found that is true (with a given instantiation of variables). The corresponding TR Action is then executed.
While Guard (with the same instantiation of variables) is true or While is true or the duration of the while part hasn't expired (since the time this rule was chosen) then this rule will continue to be the chosen rule until Until becomes true and the until duration has expired.
At that point, the guards will be checked again from the beginning. If no earlier rule guards are true, the same rule will refire if the guard is true with a different instantiation of variables (and execute the corresponding actions using the new instantiation of variables) or will continue as long as the guard remains true with the same instantiation of variables or While is true or the while duration hasn't expired. Otherwise, the guards of the rules below this rule are checked.
Note that executing TR Action will typically mean stopping durative actions from previous rule firings and starting new actions (both discreet and durative). As an optimization, instead of stopping a durative action with given arguments and starting the same action with different arguments, the system will generate a 'modify action'.
The forms of TR Action for each TR rule are given below.
TR Program
A TR Action can be a call on a TR program (possibly a recursive call on the same program). When such a rule is fired this TR program is executed.
Sequence of discreet and durative actions
A TR Action can be a comma separated sequence of discreet and durative actions. The special sequence () is the 'do nothing' action.
Timed sequence
A TR Action can be a comma separated sequence of the form
Action for Duration , ... , Action for Duration.
where the last duration can be elided.
Each action above can be either a call on a TR program or a sequence of discreet or durative actions.
When 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 will repeat until a different rule is fired. If the last duration is missing, it as treated as infinity.
Wait repeat
A TR Action can be of the form
Action Sequence wait Duration ^ Repeats
When called, the actions will be executed, and after Duration seconds the actions will be executed again. This will be repeated Repeats times unless another rule is chosen. If another rule is not chosen then an error will be generated.
Attached Qulog actions
TR Actions can have Qulog actions attached as below.
TRAction ++ Action
When called, both TRAction and Action will be executed. The Qulog action is typically a modification to the belief store or a message send action.
Example TR program
As an example of TR Programs, consider the following TR 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.
%% We assume that if the program receives a dead_centre percept
%% it will also receive a centre percept
dir::= left | right | centre | dead_centre
percept
see : (num, dir),
holding : ()
durative
move : (num),
turn : (dir)
discrete
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
get_object : () ~>
get_object {
holding & see(0, centre) ~> ()
not holding & see(0, centre) ~> grab wait 10^2
not holding ~> get_to
true ~> release wait 10^2
}
get_to : () ~>
get_to {
see(0, centre) ~> ()
see(0, Dir) ~> turn(Dir)
see(_, centre) ~> move(6)
see(_, Dir) while see(_, centre) until see(_, dead_centre)
~> move(4) , turn(Dir)
true ~> turn(left) for 10 ; move(4) for 10
}
Consider an initial state where no objects are seen and holding is false. When get_object is executed then the third rule is fired causing get_to to be executed. The last of rules of get_to will be chosen (a timed interval). This will first cause the robot to start turning for 10 seconds and then start moving for 10 seconds. This will be repeated until an object is spotted.
At some point, say see(10, left) becomes true. This causes the fourth 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 third rule should now fire because its guard becomes true. However, the until condition prevents higher rules from firing. Once, say, see(8, dead_centre) becomes true then rule three will fire. By over-achieving the guard of the third rule the "fluttering" between the third and fourth rule (without the until condition) is eliminated.
The while condition is necessary because it takes over from the guard and mantains rule four as the active rule when seeing to the left is no longer true but seeing to the centre becomes true.
On the other hand, 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 four 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 two of get_object will fire. The until only has a local effect - affecting rule choices within its own TR program.
Eventually, without interference from the environment, see(0, centre) will become true. The second 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 rule will fire causing the robot to stop.
If, however, there was a mechnical problem with the gripper, the robot will wait for 10 seconds and try to close the gripper again in the hope that the problem will disappear. If the problem doesn't clear up after two attemps (with a 10 second wait for each) then an error will be produced.
It may seem that the robot's job is done now that it has achieved its goal. However, the TR program is still monitoring the state and say the environment now removes the object from the robot's grip. Rule four will fire, opening the grippers, and then, once holding is no longer true, rule three will fire and the robot will go back to searching for an object.
Table of Contents | Overview of QuLog | Getting Started | Syntax | Built-ins | Standard Operators | Index |