4.10.2 Type Declarations

All functions, relations, actions and TeleoR procedures have type declarations of the forms

fun Name(TypeTuple)->Type

mfun Name(TypeTuple)->Type

rel Name(ModedTypeTuple)

dyn Name(TypeTuple)

mrel Name(ModedTypeTuple)

act Name(ModedTypeTuple)

tel Name(TypeTuple)

As an example, below is the type declaration for the builtin append relation (with the same semantics as the standard Prolog append relation).

rel append(!list(T), !list(T), ?list(T)),
    append(?list(T), ?list(T), !list(T)),
    append(!list(??T), !list(??T), ?list(??T)),
    append(?list(??T), ?list(??T), !list(??T)),
    append(??list(T), ??list(T), ??list(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 fifth 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] : list(atom || nat)

Here, the suitable (minimal) type for T is the union of two types.

4.10.2.1 Dynamic Relations

Dynamic relations are fact defined relations that can be updated by actions. They are declared as follows.

dyn Name(TypeTuple)

The declaration

dyn age_is(human,age)

is essentially the declaration

rel age_is(?human, ?age)

together with an implicit declaration that age_of is defined only by facts that are action updatable.

Dynamic relations are updated by primitive actions remember, forget etc.

4.10.2.2 Global Variables

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

dyn count(int)

and the definition

count(0)

with the implicit restriction that the count dynamic relation is always defined with exactly one fact.

Global variables are updated using primitive actions :=, +:=, -:=.

4.10.2.3 Automatic Memoization of Functions and Relations

By changing fun and rel in type declarations of functions and relations to mfun and mrel we are declaring that automatic memoization should take place during evaluation. For functions this means caching the results of specific calls and for relations this means caching all the answers to a given call on the relation.

The idea being that if the same call is made again we get the same answers without having to recompute the function or call the relation. The problem with this is if the function or relation either directly or indirectly calls an impure function or relation.

In QuLog there are two kinds of impure functions and relations. One kind of impurity arises when we call, for example, functions like now() or rand(). The other kind is when the function or relation queries the belief store. Since the belief store can change then the results of calling functions or relations that depend on the belief store facts can also change.

Memoization does not deal with the first kind of impurity and so functions and relations with this sort of impurity should not be so declared.

On the other hand, the second sort of impurity is automatically dealt with by the system. The system computes the dependencies on the belief store each declared memoizing function and relation has and each time the belief store is updated the system checks if the changes overlap with the depedencies of memoizing functions and relations and for those that overlap the cached results are removed and so the next call will use the original definition.

We now discuss what is remembered when a call is made on one of these functions or relations. For this discussion we consider a function to be like a relation with one more argument (for the result) with all the function arguments being in ! mode and the result argument in ? mode. To construct the instances that are to be remembered from a given call we take all the arguments that are in non-! mode and replace them by new variables. We then call this and remember all such instances. Consider the following example.

mrel p(!int, ?int)
dyn d(int, int)

p(A, B) <= d(A, B)

d(1, 1)
d(1, 2)
d(2, 4)
d(2, 5)

If the first call on p is p(1, 2) then we first remember all the solutions of p(1, X) (which is p(1, 1), p(1, 2)) and then we call p(1, 2) on this remembered information (which succeeds). If the original call was instead p(1, 3) we would still remember the same two solutions but then the call would fail.

Note that nothing is remembered with the first argument being 2. We would need to make a call like, for example, p(2, X) in order for such information to be remembered.

Also note that if we now remember (or forget) facts about d then the above remembered information will be deleted and later recomputed when a new call on p is made.

We take the above approach so as to preserve the semantics of the call i.e. any call on the relation (without remembering) should give the same answers as the same call using the remembered information.

There is a problem with preserving semantics (similar to the use of impure functions) and that is certain non-logical relations may not satisfy the required semantics. In particular, if the relation of interest has both ! moded and non-! moded arguments and we make a call with all arguments given and another call with the same ! moded arguments but with the other arguments replaced by new variables and then follow the call with a collection of unifications which link the corresponding variable arguments with the values in the first call and get different sets of answers then the relation is non-logical. Such relations should not be declared as mrel relations.

Because mixed moded arguments such as !list(?int) fall between having ! mode and having ? mode they are difficult to deal with and so we forbid such modes from appearing in mrel relation declarations.

An expected use of memoizing functions and relations is in top-level TR procedure guards where calling the functions and relations is quite expensive but the belief store facts these functions and relations depend on don’t change very often. This can increase performance when re-evaluating the call stack for the TR procedure.

4.10.2.4 Doc strings

Any declaration can be immediately followed by a string and this is taken to be a doc string for the declared relation/action/function. This is similar to doc strings in Python. When writing a doc string it is common to refer to arguments and we support this by allowing the user to attach a variable to the argument type so that it can be referred to in the doc string. As an example, below is a declaration from the builtin types.

rel re_match(RE : !string, String : !string, Match : ?list((nat, nat)))
"RE is a regular expression, String is the string to match
the RE against. If a match is found, Match is a list of 
pairs representing the start and end of matches.
On backtracking, Match is instantiated to the next list of matches."

4.10.2.5 Default arguments

When declaring code we can add the default tag to an argument and specify a default value. Below is an example from the builtin types.

act read_term(??term, Stream : !stream_type default stdin)
"Unifies its argument with the next term denoted by the next
sequence of characters in the stream followed by fullstop, return."

This allows us to call read_term with just one argument with the implicit second argument being stdin.

All default arguments must be listed at the end of the declaration and if we want to call the code with a specific value instead of a default argument then all the previous argument tagged as default must be explicitly given their default values.


On This Site