2 Syntax

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.

2.1 Data constants - the atomic term values

These are atoms (atom type), natural numbers (nat type), integers (int type), floating point numbers (num type) and strings (string type).

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

2.1.1 Atoms

There are four syntactic forms for atoms.

  1. A lower case letter followed by any sequence consisting of "_" and alphanumeric characters.
    For example:

    percy_smith_2
    semester_1

  2. Any combination of the following set of graphic characters.

    |-/+*<=>#@$\^&~:.?

    For example:

    @<=

  3. An atom of the above two forms preceded by the back quote charater "`". This form of atom is used when the sequence of characters without the back-quote has been used as the name of a relation, function or action. Such a name cannot be used as an atom unless preceded by a backquote.
    For example:

    `<>
    `append

  4. Any sequence of characters enclosed by "'" (single quote). Single quote can be included in the sequence by writing the quote twice. "\" indicates an escape sequence, where the escape characters are case insensitive. The possible escape characters are:
    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'

2.1.2 Numbers

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.

  1. Any sequence of numeric characters. This method denotes the number in decimal, or base 10.
    For example:

    123

  2. Base'Number, where Base ranges from 2 to 36 and Number can have any sequence of alphanumeric characters. Both upper and lower case alphabetic characters in Number are used to represent the appropriate digit when Base is greater than 10.
    For example, integer value 10 can be written as:

    2'1010
    16'A
    16'a

  3. Binary numbers can also be represented in the form 0b followed by binary digits. Similarly octal and hexadecimal numbers can be represented by 0o or 0x followed by digits.
    For example

    0b1011
    0o3170
    0x3afd

  4. 0'Character gives the character code of Character.
    For example,

    0'A

    gives the ASCII character code 65.

A natural number is a non-negative integer. num type numbers include double precision floating point numbers. They are represented using either a decimal point or scientific e notation. Examples:

27.8
1.896e4

2.1.3 Strings

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

2.2 Code names

Syntactically these are the same as the first two forms of atom - alphanumeric and graphic - but they are the names of primitive or program defined relations, functions or actions.
For example:

<>
append

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.

2.3 Variables

Variables are available in three syntactic forms.

  1. An upper case letter followed by any sequence consisting of "_" and alphanumeric characters.
    For example:

    List_nums Head

  2. "_", followed by an upper case letter, and then any sequence consisting of "_" and alphanumeric characters.
    For example:

    _Dictionary _X_1

  3. "_" alone denotes an anonymous variable. Repeated occurences of underscore in a query or rule denote different unnamed variables.

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.

2.4 Compound Terms

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.

2.5 Function calls

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

2.6 Lists

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.

2.6.1 List compehension expressions

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.

2.7 Sets

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}

2.7.1 Set compehension expressions

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.

2.8 Programs

A Qulog program comprises a sequence of:

type definitions,
type declarations,
relation rules (aka clauses),
action rules,
function rules.

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.

2.8.1 Type Definitions

A type definition is of the form

type-name ::= type-expression

type-name is either an alphanumeric atom or a single argument compound term whose only argument is a variable (representing any type). A type definition with such a type name defines a parameterised type where the type variable stands for any type. That type variable then appears in one or more of a disjunction of compound terms with other arguments that are type names. We give examples below.

type-expression is another user or system defined type, in which case the type definition is essentially a type alias, for example
speed ::= num
More usually it is one of the following type expressions defining a new data type.

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.

2.8.2 Type Declarations

All functions, relations, actions and teleoR programs have type declarations of the form

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.

2.8.3 Simple Conditions for Rules and Relation Queries

These comprise predications, negated predications and meta-calls.

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

not Predication

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.

once (SimpleConj)

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 Predication

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.

2.8.4 Relation Definitions

A relation definition consists of a sequence of rules (clauses) of the form

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

2.8.5 Action Definitions

An action definition consists of a sequence of rules of the form

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

2.8.6 Function Definitions

A function definition consists of a sequence of rules of the form

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.

2.8.7 TR Program Definitions

A TR-program definition has the form

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