Language Reference/Terms

From wiki.visual-prolog.com

This section describes terms and how execution/evaluation of terms and clauses proceeds.

Semantically, there are two kinds of terms: formulas and expressions.

  • Expressions represent values, like the number 7.
  • Formulas represent logical statements, like "the number 7 is greater than the number 3".

Syntactically the two kinds have a huge overlap and therefore the syntax unites the two kinds into terms.

The following definition of Term is simplified, in the sense that it includes syntactic constructions that are not legal. For example, one cannot legally write ! + !. We do however believe that using this simple syntax description in combination with intuitive understanding of language concepts, the type system, and the operator hierarchy described below is better for most purposes.

Term:
    ( Term )
    Literal
    Variable
    Identifier
    MemberAccess
    PredicateCall
    PredicateExpression
    UnaryOperator Term
    Term Operator Term
    Cut
    Ellipsis
    FactvariableAssignment

Backtracking

The evaluation of a Prolog program is a search for a "solution" to the goal. Each step in the search for a solution can either succeed or fail. At certain points in the program execution there are more than one possible choices for finding a solution. When such a choice point is met a so called backtrack point is created. A backtrack point is a recording of the program state plus a pointer to the choice that was not executed. If it turn out that the original choice could not provide the solution (i.e. if it fails), then the program will backtrack to the recorded backtrack point. Thereby restoring the program state and pursuing the other choice. The mechanism will be described and exemplified in details in the following sections.

Literals

Literals have universal type.

Literal:
   IntegerLiteral
   RealLiteral
   CharacterLiteral
   StringLiteral
   BinaryLiteral
   ListLiteral
   CompoundDomainLiteral

See also Literals (in Lexical Elements)

Variables

Variables in Visual Prolog are immutable: once they are bound to a value they retain that value, but backtracking can unbind the variable again during the process of restoring a previous program state.

A variable can thus be bound (during unification and matching), if it is already bound then it evaluates to the value that it is bound to.

Variables are names starting with an upper-case letter or with an underscore (_), followed by a sequence of letters (both uppercase and lowercase), digits, and underscore characters (all in all called an UppercaseIdentifier):

Variable:
    UppercaseIdentifer

The following are examples of valid variable names:

My_first_correct_variable_name
_
_Sales_10_11_86

while the next two are invalid:

1stattempt
second_attempt

The variable consisting of single underscore character (i.e. _) is known as the anonymous variable. The anonymous variable is used in patterns and bindings where the corresponding value is of no interest and should be ignored. Every occurrence of the anonymous variable is an independent anonymous variable, i.e. even though the anonymous variable is used several times in a single clause they have no relation to each other.

If variables that starts with an underscore are not anonymous, but they are still intended for values of no interest that should be ignored. The compiler will issue a warning if the value of such a warning is actually not ignored.

Prolog variables are local to the clause in which it occurs. That is, if two clauses each contain a variable called X, these X-s are two distinct variables.

A variable is said to be free when it is not yet associated with a term and to be bound or instantiated when it is unified with a term.

The Visual Prolog compiler does not make a distinction between upper and lower case letters in names, except for the first letter. This means that the two variables SourceCode and SOURCECODE are the same.

Identifier

Identifier: one of
    MemberName
    GlobalScopeMembername
    ScopeQualifiedMemberName
 
MemberName:
    LowerCaseIdentifier

Identifiers are used to refer to named entities (i.e. classes, interfaces, constants, domains, predicates, facts, ...).

An identifier can just be a lower case identifier (i.e. a lowercase letter followed by a sequence of letters, numbers and underscore characters).

Many entities can have the same name. So it may be necessary or desirable to qualify the lowercase identifier the name of the particular scope of interest, or to state that the name is in the global namespace.

Example These are examples of unqualified identifiers:
integer
mainExe
myPredicate

Global Entities Access

The only global entities, which exist in Visual Prolog, are built-in domains, predicates, and constants. Global names are directly accessible in any scope. There might however exist situations where a global name is shadowed by a local or imported name. In that case the global entity can be qualified with a double colon '::' (without a prefixed class/interface name). The double colon can be used everywhere, but the most important place is where an interface name is used as formal parameter type specifier.

GlobalScopeMemberName:
    :: MemberName
Example The built-in domain integer is defined in the global scope, to avoid ambiguity or stress that it is this particular domains you can use the global scope member name:
::integer

Class/Interface Member Access

Static members of classes and interfaces are accessed by means of qualification with the class name (and optionally a namespace prefix):

ScopeQualifiedMemberName
    NamespacePrefix-opt ScopeName :: MemberName
 
NamespacePrefix:
    NamespaceIdentifier-opt \
 
ScopeName:
    LowercaseIdentifier

The ScopeName is the name of the class or interface that defines/declares the name.

Namespace prefixing is explained in: Referencing names in namespaces.

Some names can be accessed without qualification, see scoping & visibility.

Predicate Call

A predicate call have the form

PredicateCall:
     Term ( Term-comma-sep-list-opt )

The first term must be an expression that evaluates to a value with predicate type. Typically, it is either the name of a predicate in a class, or an expression that evaluates to a predicate member of an object.

Notice that some predicates return values, whereas other predicates do not. A predicate that returns a value is an expression, and the predicate call is often referred to as a function call. A predicate that does return a value is a formula.

A predicate is invoked by applying arguments to the predicate. The predicate must have a flow-pattern that matches the free/bound state of the arguments.

Most predicates are defined by a set of clauses, but some predicates are built into the language and some are defined externally in a DLL (perhaps in a foreign programming language).

When a predicate is invoked by a predicate call, each clause is executed in turn until one of them succeeds, or there are no more clauses left to execute. If no clause succeeds the predicate fails.

If a clause succeeds and there are more relevant clauses left, a backtrackpoint is created to the next relevant clause.

Thus, a predicate can fail, succeed, and even succeed multiple times.

Each clause has a head and optionally a body.

When a predicate is called the clauses are tried in turn (from top to bottom). For each clause the arguments in the head is unified with the arguments from the call. If this unification succeeds then the body of the clause (if present) is executed. The clause succeeds, if the match of the head succeeds and the body succeeds. Otherwise it fails.

Example
clauses
   ppp() :- qqq(X), write(X), fail.
 
   qqq(1).
   qqq(2).
   qqq(3).

When ppp is called it in turn calls qqq. When qqq is called, it first creates a backtrack point pointing to the second clause. Then the first clause is executed. Hereby the free variable X from ppp is matched against the number 1, whereby X is bound to 1.

In ppp X (i.e. 1) is written and then fail cause backtracking to the backtrackpoint. Hereby program control is set to the second clause in qqq and the program state is set back to the state it was in when qqq was first entered, i.e. X in ppp is unbound again.

Before the actual execution of the second clause in qqq begins a backtrack point to the third clause is created. The execution then proceeds as it did for 1

Unification

When a predicate is called the arguments from the call is unified with the terms in the head of each clause.

Unification is the process of binding variables in such a way that two terms become equal, making as few bindings as possible (i.e. leaving as much as possible open for further binding).

Variables can be bound to any kind of terms, including variables or terms containing variables.

Unification is either possible or impossible, i.e. it can succeed or fail.

Variables and terms to which they are unified have types, a variable can only be bound to a term of the same type as the variable, or a subtype. When two variables are bound to each other they must therefore have exactly the same type.

Unification takes place (as mentioned) between a predicate call and the clause head. It also takes place when two terms are compared for equality.

Example Consider two terms (of the same type):
T1 = f1(g(), X, 17, Y, 17)
T2 = f1(Z, Z, V, U, 43)

We will attempt to unify these two terms from left to right (i.e. a left-to-right pre-traversal).

Both T1 and T2 are f1/5 terms, this match. Therefore we attempt to unify each of the arguments from T1 with each correspondent argument of T2. First we must unify Z and g(), this can be unified if we bind Z to g(). So far everything is fine and we have the first binding in our unifier:

Z = g()

The next two arguments are X and Z, which already has been bound to g(). These two arguments can also be unified if we also bind X to g(). So we now have the following contributions to our unifier:

X = Z = g()

Next we must bind V to 17 and then we must bind Y to U. So far everything unifies with the following unifier:

X = Z = g()
V = 17
Y = U

The two unified terms are now equivalent to these terms:

T1 = f1(g(), g(), 17, Y, 17)
T2 = f1(g(), g(), 17, Y, 43)

But we have not yet unified the two last arguments, which are 17 and 43. No variable binding can make these terms equal, so all in all the unification fails.

T1 and T2 cannot be unified.

In the example above T1 could have been a predicate call and T2 a clause head. But they could also have been two terms that were compared with equal "=".

Matching

Matching is the same as unification except that variables can only be bound to grounded terms. A grounded term is a term that does not contain any unbound variables.

It is the flow-patterns that are stated for predicates, that make it possible to use matching rather than full-blown unification.

Example
clauses
   ppp(Z, Z, 17).
   qqq() :-
      ppp(g(), X, 17).

Unification of the ppp-call with the ppp-clause is possible with the following unifier:

Z = X = g()

If ppp have the flow (i,o,i) then the unification is just a match:

  • g() is input as the first argument, this is bound to Z
  • The second argument in the clause is therefore bound and can thus be output to X, which therefore becomes bound to g().
  • finally the third argument is 17 used as input this number is simply compared to the third argument in the clause.

It is the flow-pattern that makes it possible to predict that the clause does not need real unification.

Nested Function Calls

Terms that have to be unified or matched with each other are allowed to contain sub-terms that are actually expressions or function calls that have to be evaluated before the unification/matching can be completed.

The evaluation of such sub-terms is done on a by-need basis.

In a predicate call all input arguments are evaluated before the predicate is called, all output arguments are variables, which does not need evaluation.

Clause heads can also contain terms that have to be evaluated, before matching/unification can be determined.

  • all matching/unification that does not require any evaluation is performed before any evaluation is performed;
  • then evaluation corresponding to input arguments is performed one by one left-to-right. Comparing each value to the corresponding input after each evaluation;
  • then the clause body is evaluated;
  • then the output arguments are evaluated (left-to-right);
  • then the return value (if the predicate is a function) is evaluated.

If any of these fail then the rest of the evaluation is not carried out.

All in all the base principles are:

  • input after other match, before body evaluation
  • output after body evaluation
  • left-to-right

Arguments

This section describes named, default, optional parameters and functor originals. Even though these notions are individual they also have a significant impact on each other and therefore they are described together.

In brief:

  • Named parameters: In a call the actual arguments can be supplied by formal parameter name rather than position.
  • Default parameters: A default parameter value can be stated together with a formal argument in a predicate declaration. This value is used when no actual parameter is supplied.
  • Optional parameters: An actual parameter is optional and can therefore be skipped.
  • Functor originals: Functor term which is used as/bound to the original of another functor term (in a functor term expression).

Named parameters

The formal parameter names from a declaration can be used to specify the actual arguments in a call by using the syntax :<Formal> = <Actual>.

Example Given the predicate addPerson:
class predicates
    addPerson : (string Name, integer Height, integer Weight).

You can call addPerson using positional arguments like this:

addPerson("Hans", 175, 75)

But you can also use named parameters like this

addPerson(:Name = "Hans", :Height = 175, :Weight = 75)

Where Name, Height and Weight are the names that is used in the declaration.

When using named parameters the order of the parameters are insignificant, so this call has the same meaning:

addPerson(:Weight = 75, :Name = "Hans", :Height = 175)

Poisitional and named parameters can be mixed such that the first arguments are given by position and the last by name. Here the Name is given by position and Weight and Height are given by name

addPerson("Hans", :Weight = 75, :Height = 175)

Notice that all positional arguments must be to the left of the first named argument.

Default parameters

A default parameter is defined by adding = <value> after a formal input parameter in a declaration (<value> can be a constant expression, a property or predicate call).

Example The predicate addPerson has three parameters Name, Height and Weight::
class predicates
    addPerson : (string Name, integer Height = 175, integer Weight = 75).

Height has default value 175 and Weight has default value 75.

Notice that it is illegal to provide default parameters for [out] parameters.

Optional parameters

If a parameter is [out] or if it has a default value, then the actual argument is optional and can be skipped provided that it is the last argument in the call. Skipping an [out] parameter corresponds to supplying an anonymous variable for that parameter. Skipping a parameter that has a default value corresponds to supplying the default value for the parameter.

Example Given this predicate
class predicates
    ppp : (integer A = 17, integer B [out]).

We can skip B because it is the last parameter and [out], so here we supply 12 for A and ignore the B output:

ppp(12) % corresponding to ppp(12, _)

In the call above 12 is the last argument, but there is a default value for that parameter so that argument can also be skipped:

ppp() % corresponding to ppp(17, _)

Using named arguments we can exchange the order of the parameters:

ppp(:B = Out, :A = 17)

In that call the last parameter has a default value so it can be skipped:

ppp(:B = Out) % corresponding to ppp(B: = Out, :A = 17) --> ppp(17, Out)

An optional out parameter can cause conflict with another predicate:

Example Conflicting predicates
predicates
    ppp : ().
    ppp : (integer X [out]). % conflict

These predicate declarations conflicts because a call without arguments p() could be to either of them.

The attribute [mandatoryOut] can be used to avoid such conflicts and/or if it doesn't seem appropriate that a predicate a has optional output parameters.

Example No conflict because the second predicate does not have optional output parameters:
predicates
    ppp : ().
    ppp : (integer X [out]) [mandatoryOut]. % no conflict

These predicate declarations conflicts because a call without arguments p() could be to either of them.

Functor originals

A functor original is a syntactic construction that describes an original for a functor term expression. Syntactically it takes the following form:

<functor>(<arguments> | <functor original> )

The functor original can either be a a functor value or a free variable. In both cases the construction indicates that the functor may have more arguments than those mentioned explicitly in front of the bar.

If the functor original is a functor value then it will be used as original for constructing a new functor value.

Example Consider this code:
domains
    person = person(string Name, integer Height, integer Weight).
 
class predicates
    updateName : (person Person, string NewName) -> person Updated.
clauses
    updateName(Person, NewName) = person(NewName | Person).

Here we use Person as original in the functor term expression person(NewName | Person). This expression will create a new person value which has NewName as the Name/first component. All the remaining functor components (i.e. Height and Weight) are taken from the original, i.e. Person.

The use of functor originals can be combined with named parameters:

domains
    person = person(string Name, integer Height, integer Weight).
 
class predicates
    updateWeight : (person Person, Integer NewWeight) -> person Updated.
clauses
    updateWeight(Person, NewWeight) = person(:Weight = NewWeight | Person).

Here NewWeight is used as Weight component. And again the remaining functor components (i.e. Name and Height) are taken from the original, i.e. Person.

The functor original can be any kind of term as long as it evaluates to an appropriate functor term.

Example Here the functor original is the result of calling a function:
clauses
    mkPerson(Name) = person(Name | getCurrentPerson()).

If a functor domain has more than one alternative, then the functor original must be of same kind as the term that is constructed.

Example Consider a functor domain with two alternatives:
domains
    transport =
        aircraft(string Name, integer Seats, integer Range);
        car(string Name, integer Doors).

The following is legal:

A1 = aircraft("Cruiser 1", 25, 300),
A2 = aircraft("Cruiser 2" | A1) % legal A1 is an aircraft

Because A1 is an aircraft, and it is an aircraft we are constructing (as A2).

This is illegal:

C = car("Taxi 23", 4),
A = aircraft(:Seats = 25, :Range = 173 | C)  % illegal C is a car

Because C is a car but we are constructing an aircraft, it does not matter/help that a car has a Name component, which is what we need for the aircraft we are constructing.

In a functor match the functor original can be an anonymous variable. And in that it simply indicates that the term can have more components which we don't care about.

Example Given the transport domain above, we can write a predicate that extract the Name component like this:
class predicates
    getName : (transport Transport) -> string Name
clauses
    getName(aircraft(Name | _)) = Name.
    getName(car(Name | _)) = Name.

Here we have used an anonymous variable as functor original indicating that the functor term has/may have more arguments (which we are not interested in).

In the car case the functor original only represents one additional argument, but using this form the code will be robust to adding additional components to car later. For the same reason, it can make sense to provide a functor original that doesn't represent any additional arguments (i.e. that represents zero extra arguments).

We could also have used named parameters:

class predicates
    getName : (transport Transport) -> string Name
clauses
    getName(aircraft(:Name = Name | _)) = Name.
    getName(car(:Name = Name | _)) = Name.


Here is a predicate that determines whether a transport is a car:

class predicates
    isCar : (transport Transport) determ.
clauses 
    isCar(car( | _)).

This example also shows that it is not necessary to provide any regular arguments at all besides the functor original.

In a functor match the functor original can be a named variable. If the variable is bound then it is used as an original for constructing a functor value and then that functor value is used in the match.

Example Consider this code
C = car("Taxi 23", 4),
car("Taxi 24" | C) = getCurrentCar()
In the second line C is used as original creating the term car("Taxi 24", 4). This term is then matched against the result of calling getCurrentCar.

If the functor original is a free variable (in a functor match) then that variable will be bound to the entire functor term:

Example Consider this code where Free is a free variable
car("Taxi 24" | Free) = getCurrentCar(),

This code is equivalent to this code:

Free = getCurrentCar(),
car("Taxi 24" | _) = Free,

I.e. Free is bound to the result of calling getCurrentCar and then it is matched to this functor pattern car("Taxi 24" | _).

Notice that the code will fail (and not raise an exception) if getCurrentCar returns an aircraft.

Also consider this code (where Name and Car are both free variables):

car(Name | Car) = getCurrentCar(),

it equivalent to this code

Car = getCurrentCar(),
car(Name | _) = Car


Fact Variable Assignment

Assign operator := is used to assign a new value for a fact variable FactVariable. The Term must be evaluated to a value of suitable type (i.e. the same type as the fact variable, or a subtype).

FactVariableAssignment:
    FactVariable := Term

Facts

A fact database contains a number of fully instantiated (grounded) predicate heads corresponding to the facts from the facts section declaration. The facts can be accessed by a predicate call, using the fact name as the predicate name. The predicate call is matched against each fact in turn; succeeding with a possible backtrack point to the next fact each time the predicate call match the fact. When there are no more facts in the fact database then the predicate call fails.

New facts can be asserted using the predicates assert/1, asserta/1, and assertz/1. assert/1 is the same as assertz/1 and it asserts a new fact to the end of the list of facts, whereas asserta/1 asserts a new fact to the start of the list.

Existing facts can be retracted with the predicate retract/1 and retractAll/1. retract/1 retracts the first fact that match the argument binding variables in the argument and leaving a backtrack point so that more facts will potentially be retracted when backtracking.

retractAll/1 retracts all facts that matches the arguments and succeeds without any binding.

Operators

Operators are organized in a precedence hierarchy. In the rule below operators in each group have same precedence, which is higher than those below. I.e. the power operator has higher precedence than unary minus and plus, which in turn has higher precedence than the multiplication operators, etc. Parenthesis can be used to circumvent the precedence (and for clarification).

Operator: one of
    PowerOperator
    UnaryOperator
    MultiplicationOperator
    AdditionOperator
    OtherwiseOperator
    RelationOperator
    MustUnifyOperator
    InOperator
    AndOperator
    OrOperator
UnaryOperator: one of
    - +

All operators except the UnaryOperator's are binary. The power operator is right associative, all other operators are left associative.

RelationOperator, MustUnifyOperator and InOperator have same precedence.

Notice that the placement UnaryOperator is not consistent with mathematics, where these operators are at the same level as the AdditionalOperator's. The difference has no influence of the calculated value, but it allows writing 2*-2, where mathematics would require a parenthesis around the second operator 2*(-2). It also means that -2*2 is mmeans (-2)*2 where it would be -(2*2) in mathematics (the resulting value is the same).

Example

-2^2 is the same as -(2^2) because ^ has higher precedence than unary minus.

Example

3^2^2 is the same as 3^(2^2) because ^ is right associative.

Example

-2*-3^-4+5 is the same as ((-2) * (-(3 ^ (-4)))) + 5.

Example The following term:
7 + 3 * 5 * 13 + 4 + 3 = X / 6 ; A < 7,  p(X)

has the same meaning as this term:

((((7 + ((3 * 5) * 13)) + 4) + 3) = (X / 6)) ; ((A < 7) , p(X))

I.e. at outermost level the term is an "or" of two terms, the first of these is a relational (=) term, the second is an "and" term, etc.

Arithmetic Operators

The arithmetic operators are used for arithmetic operations on numbers. They are expressions, which takes expressions as arguments. They have root types as arguments and return universal types as result. (See Universal and Root types.)

PowerOperator:
    ^
MultiplicationOperator: one of
    * / div mod quot rem
AdditionOperator: one of
    +  -

Relational Operators

The relational operators are formulas, which takes expressions as arguments. Given this nature they are non-associative.

RelationOperator: one of
  =   >   <   >=   <=   <>   ><

First the left term is evaluated, then the right term is evaluated and then the results are compared.

Notice that <> (different) is not the dual operation of = (equal). <> compares two values, whereas = tries to unify two terms (in the general case at least).

The dual to expression A = B is not (A = B).

Must Unify Operator

The must unify operator is a procedure, which takes expressions as arguments. It is non-associative.

MustUnifyOperator:
  ==

A == B unifies A and B; if the unification fails an exception is raised, otherwise the predicate succeeds. Therefore A == B always succeeds.

Example
clauses
    p(L) :-
        [H|T] == L,
        ...
p is a procedure. An exception will be raised if it is called with an empty list, because [H|T] and L cannot unify.

Bitwise and boolean operators

The following operators for bitwise operations on unsigned, unsigned64 and unsignedNative.

A ** B % A and B
A ++ B % A or B
A -- B % A and not B
A ^^ B % A exclusive or B
~~ A % not A
A << N % Shifts the bits in A N times to the left.
A >> N % Shifts the bits in A N times to the right.

The logical operations (**, ++, ^^ and ~~) can also be used on boolean values.

The shift operations discard bits that is shifted out of the number, and shift-in zero bits in the opposite end.

Logical Operators

The AndOperator(s) and OrOperator(s) are formulas, which takes formulas as arguments. They are all left associative. The , and and are synonyms and so are ; and or.

AndOperator: one of
 ,   and
OrOperator: one of
  ;  or orelse

and (,)

The evaluation of an and term A, B proceeds as follows. First the left sub-term A is evaluated. If this evaluation fails, the whole and term fails. If A succeeds then the right sub-term B is evaluated. If this evaluation fails, the whole and term fails, otherwise the and term succeeds.

Thus the second sub-term B is only evaluated, if the first sub-term A succeeds.

Example
clauses
   ppp() :-
       qqq(), rrr().

When ppp is called it will first call qqq and if qqq succeeds, then it will call rrr. If rrr succeeds, then the and term and subsequently the whole clause succeeds.

or (;)

The evaluation of an or term A; B proceeds as follows. First a backtrack point to the second term B is created and then the first term A is evaluated. If the evaluation of the first term succeeds, then the whole or term succeeds and is left with a backtrack to the second term B. If the evaluation of the first term fails, the backtrack point to the second term is activated.

If the backtrack point to the second term B is activated (either because the first term fails, or because something later in the execution invokes the backtrack point), then the second term B is evaluated and the whole or term will succeed if B succeeds.

Thus an or term can succeed with a backtrack point and the second sub-term B is only evaluated on backtrack.

Example
clauses
   ppp() :-
       (V = 3 or V = 7), write(V), fail.

Here we have used the keyword or, but you can also use semi-colon ;.

When ppp is called we first create a backtrack point to the term V = 7 and then we evaluate V = 3. Thereby V will be bound to 3 we then continue to the write(V) after 3 has been written fail is met. fail always fails so we effectuate the backtrack point leading us to the term V = 7.

Backtracking also undo all bindings made since the backtrack point was created. In this case it means that V is unbound.

Then V = 7 is evaluated and V becomes bound to 7 and er continue to the term write(V), and then fail is met again, this time there are no more backtrack points ppp fails.

Using parentheses or can be nested deeply in clauses.

clauses
   p(X) = Y :-
       (X = 1, !, Z = 3 or Z = 7), Y = 2*Z.

We recommend careful usage of or. It is mainly intended for usage in test-conditions:

clauses
   isOutside(X) :-
       (X < 10 or X > 90), !.

or is a nondeterministic construction, but orelse can be used as a deterministic pendant:

clauses
   isOutside(X) :-
       X < 10 orelse X > 90.

orelse

orelse is a deterministic pendant to the nondeterministic or. A orelse B will succeed if A succeeds or if B succeeds, but it will not leave a backtrack point to B if A succeeds.

The evaluation of an orelse term A orelse B proceeds as follows: First a backtrack point to the second term B is created and then the first term A is evaluated. If the evaluation of the first term succeeds then the backtrack to the second term (and any backtrack point within it) B are removed again and the whole orelse term succeeds. If the evaluation of the first term A fails, the backtrack point to the second term B is evaluated.

So an orelse term does not leave a backtrack point.

Example
clauses
   ppp(V) :-
       (V = 3 orelse V = 7), write(V).

Whenever ppp is called we first create a backtrack point to the term V = 7 and then we evaluate the term V = 3, if V = 3 succeeds we remove the backtrack point to V = 7 again and then continue to write(V). If V = 3 fails the backtrack point to V = 7 is effectuated. If V = 7 succeeds we continue to write(V), if V = 7 fails the entire ppp predicate will fail.

otherwise

otherwise is an expression operator; though it has control flow that makes it resemble the logical operators.

A otherwise B

is an expression (A and B must be expressions). If the evaluation of A succeeds by evaluating to the value VA then A otherwise B evaluates to VA, otherwise (i.e. if the evaluation of A fails) then A otherwise B will be the result of evaluating B.

otherwise is right associative:

A otherwise B otherwise C
=>
A otherwise (B otherwise C)

It has lower precedence than all other expression operators, but higher than relational operators:

V < A + tryGet() otherwise 9
=>
V < ((A + tryGet()) otherwise 9)

core have been enriched with a predicate isSome for providing default values for core::optional matching:

Example
V = isSome(Optional) otherwise 0
If Optional have the form some(X) then V will get the value X otherwise V will get the value 0.

not

The not/1 takes a term as the argument. The evaluation of not(A) first evaluates A. If A succeeds, then not(A) fails, if A fails, then not(A) succeeds.

Notice that not(A) will never bind any variables, because if not(A) succeeds then A has failed, and a failed term does not bind anything. If not(A) on the other hand fails, it cannot bind any variables either, because then the term itself failed.

Also notice that not(A) can never succeed with backtrack points, because if not(A) succeeds then A have failed, and a failed term cannot contain any backtrack points. This in turn means that all possibilities of success in A have been exhausted.

cut (!)

Cut "!" removes all backtrack points created since the entrance to the current predicate, this means all backtrack points to subsequent clauses, plus backtrack points in predicate calls made in the current clause before the "!".

Cut:
    !
Example
clauses
   ppp(X) :-
       X > 7,
       !,
       write("Greater than seven").
   ppp(_X) :-
       write("Not greater than seven").

When ppp is executed, there is first created a backtrack point to the second clause, and then the first clause is executed. If the test "X > 7" succeeds then the cut "!" is reached. This cut "!" will remove the backtrack point to the second clause.

Example
clauses
   ppp() :-
       qqq(X),
       X > 7,
       !,
       write("Found one").
   ppp() :-
       write("Did not find one").
clauses
   qqq(3).
   qqq(12).
   qqq(13).

When ppp is executed it first creates a backtrack point to the second ppp clause and then qqq is called. The qqq will create a backtrack point to the second qqq clause and execute the first clause, thereby returning the value 3. In ppp variable X is bound to this value and then compared to 7. This test fails and, therefore, the control backtracks to the second clause of qqq.

Before executing the second clause a new backtrack point to the third clause of qqq is created and then the second clause returns 12.

This time the test against 7 succeeds and, therefore, the cut is executed. This cut will remove both the backtrack point left in qqq as well as the backtrack point to the second clause of ppp.

cut Scopes

A cut scope is a scope to which the effect of a cut is limited. Meaning that if a cut is met within a cut scope then only backtrack points within that scope are discarded, while backtrack points outside (i.e. prior to) the cut scope remains.

The clauses of a predicate is a cut scope. Meeting a cut will (at most) discard the backtrack points that was created after entrance to the predicate. Backtrack points created before entrance to the predicate will remain.

Example
clauses
   aaa() :- p1_nd(), qqq().
   qqq() :- p2_nd(), !.

aaa calls p1_nd, which leaves a backtrack point, and then it calls qqq. qqq calls p2_nd, which also leaves a backtrack point. Then we meet a cut. This cut is in the cut-scope of the qqq predicate, so it is only the backtrack point in p2_nd which is discarded, the one in p1_nd remains.

Several terms introduce cut scopes (see the respective terms: list comprehension, if-then-else, foreach). Here we will use if-then-else to illustrate the effect of cut scopes. Consider the schematic if-then-else term:

if Cond then T1 else T2 end if

The condition Cond is a cut-scope, meaning that a cut inside Cond will only have effect inside Cond. Cuts inside T1 and T2, on the other hand, have effect outside the if-then-else statement.

Consider this code fragment:

X = getMember_nd([3,1,2]),
if X = getMember_nd([3,3]), ! then
   write(X)
else
   !
end if,
fail

getMember_nd is a nondeterministic predicate. The evaluation of this code will go as follows. First X is bound to 3 and getMember_nd leaves a backtrack point (so that X can later become 1 and then even 2).

Then we evaluate the condition in the if-then-else term. The first part of this condition succeeds as 3 is a member of [3,3]. The first part also leaves a backtrack point, so that it can be examined whether X is a member several times.

Now we meet a cut. This cut is inside the condition part of an if-then-else statement, so it only has local effect, meaning that it only discards the backtrack point in the second getMember_nd, but leaves the backtrack point in the first getMember_nd predicate.

The whole condition succeeds and we enter the then-part and write out "3".

After the if-then-else we meet fail, which backtracks us to the first getMember_nd.

getMember_nd then binds X to 1, and leaves a backtrack point (so that X can later become 2).

Then we evaluate the condition in the if-then-else term. The first part of this condition fails as 1 is not a member of [3,3]. So we enter the else-part.

Here we meet a cut. This cut is in the else-part of a conditional term so it has effect outside the if-then-else term and subsequently it discards the backtrack point in the first getMember_nd.

When we meet the fail after the if-then-else term there are no more backtrack points in the code and it will fail. So all in all X never becomes 2.

fail/0 and succeed/0

fail/0 and succeed/0 are two built-in nullary predicates. fail/0 always fails and succeed/0 always succeeds, besides this the predicates have no effect.

in

InOperator:
  in

The in operator is used to test for member ship of a collection (e.g. a list) and to nondeterministically generate the members of a collection.

Example
predicates
    p : (Type X, Type* L).
clauses
    p(X, L) :-
        if X in L then
            write("X is in L\n")
        end if.

p is a procedure that takes a value X and a list L as arguments. If X is in the list L then it will write "X is in L". In this case in is used as an in-test (membership test).


Example
predicates
    q : (Type* L).
clauses
    q(L) :-
        foreach X in L do
            writef("% is in L\n", X)
        end foreach.
q is a procedure that takes a list L as argument. The "in" operator is used to nondeterministically return the members of L, so that they can be written.

The in operator can be defined for any domain and interface using the in_test and in_iterate attributes.

The in_test(<predicate name>) attribute defines the predicate that is used as in-test for a certain domain or interface. Likewise the in_iterate attribute defines the predicate that is used as in-iterator for the domain/interface.

Example
domains
    tree{E} = empty; node(tree{E} Left, E Value, tree{E} Right)
        [in_test(isMemberTree), in_iterate(getAll_nd)].

When the program contains A in B where A is bound B is a tree{E} then isMemberTree is actually called.

In that case A in B corresponds to isMemberTree(A, B).

If A is free the call corresponds to A = getAll_nd(B).

For a domain <collection> the predicate must have the type:

predicates
    <predicate> : (<some-type> Elem, <collection> Collection) determ.
Example
interface collection [in_test(contains), in_iterate(getAll_nd)]
...
end interface collection

When the program contains A in B where A is bound B is a collection then contains is actually called.

In that case A in B corresponds to B:contains(A).

If A is free the call corresponds to A = B:getAll_nd().

For a domain <collection> the in_test and in_iterate predicate must fulfill these schematic declarations:

domains
    <collection> = ... [in_test(<in_test>), in_iterate(<in_iterate>)].
class predicates
    <in_test> : (<some-type> Elem, <collection> Collection) determ.
    <in_iterate : (<collection> Collection) -> <some-type> Elem nondeterm.

For an interface <collection> the in_test and in_iterate predicate must fulfill these schematic declarations:

interface <collection> [in_test(<in_test>), in_iterate(<in_iterate>)]
predicates
    <in_test> : (<some-type> Elem) determ.
    <in_iterate : () -> <some-type> Elem nondeterm.
...
end interface <collection>

The in operator is predefined on list domains, and in PFC the collections have suitable attributes.

Example
clauses
    p() :-
        foreach X in [1, 2, 3, 4] do % in_iterate
            if X in [2, 4] then % in_test
                ...
            end if
        end foreach.

The first in is the predefined in_iterate for the list domain, and the second one is the predefined in_test.

clauses
    q() :-
        M1 = setM_redBlack::new(),
        M1:inset("a"),
        M1:inset("b"),
        M2 = setM_redBlack::new(),
        M2:inset("b"),
        foreach X in M1 do % in_iterate
            if X in M2 then % in_test
                ...
            end if
        end foreach.

For collections the in operators resolve to contains and getAll_nd:

interface collection{@Type}
    [in_test(contains), in_iterate(getAll_nd)]
 
predicates
    contains : (@Type Value) determ.
    % @short Succeeds if the collection contains the value @Type
    % @end
 
predicates
    getAll_nd : () -> @Type Value nondeterm.
    % @short @Type is nondeterministic iteration of the elements in the collection.
    % @end
 
...
 
end interface collection

List Comprehension

ListComprehensionTerm :
    [ Term || Term ]

The list comprehension term is a list expression. Consider this schematic term:

[ Exp || Gen ]

Gen is (typically) a nondeterm term. Exp is evaluated for each solution of Gen, and the resulting Exp's are collected in a list. The Exp corresponding to the first solution of Gen is the first element in the list, etc. This list is the result of the list comprehension term. Exp must be procedure (or erroneous). Both Exp and Gen are cut scopes.

The list comprehension (normally) reads: The list of Exp's such that Gen.

Example
[ X || X = getMember_nd(L), X mod 2 = 0 ]

This reads the list of X's such that X is in L and X is even. So this expression is the even numbers of L.

Example
[ X + 1 || X = getMember_nd(L), X mod 2 = 0 ]

Here the collected expression is more complex. This makes say the term more awkward:

"the list of (X+1)'s such that ..."

This expression again finds the even elements in L, but the resulting list contains all these values incremented.

This term is completely equivalent to this term:

[ Y || X = getMember_nd(L), X mod 2 = 0 , Y = X+1 ]

This term is easier to say:

"The list of Y's such that (there exists an) X which is member of L, and which is even, and Y is X+1."

Anonymous Predicates

An anonymous predicate is an expression that evaluates to a predicate value. The predicate value can be bound to a variable, passed as arguments or returned as result, but the value does not have a name in any class, interface or implementation.

Anonymous predicates have the ability to capture values from the context in which the expression occurs, this is a rather powerful ability that can be used to avoid rather excessive amount of strange/unpleasant code.

Syntax

Anonymous predicates are terms:

Term : one of
    ...
    AnonymousPredicate
    ...

An anonymous predicate is a nameless clause in curly brackets. Certain parts are optional, giving these forms:

AnonymousPredicate : one of
    { ( Arg-comma-sep-list ) = Term } 
    { ( Arg-comma-sep-list ) = Term :- Term } 
    { ( Arg-comma-sep-list ) :- Term }
    { = Term } 
    { = Term :- Term } 
    { :- Term }

Leaving out the argument list means "the required number of arguments" and can be used whenever the arguments are not used.

Semantics

An anonymous predicate expression evaluates to a predicate value. Consider this code:

clauses
    run() :-
        Inc = { (X) = X+1 },
        A = Inc(4),
        B = Inc(23),
        stdio::writef("A = %, B = %", A, B).

Inc becomes an increment predicate, so the program will write:

A = 5, B = 24

The code in this example corresponds to this code:

clauses
    run() :-
        Inc = inc,
        A = Inc(4),
        B = Inc(23),
        stdio::writef("A = %, B = %", A, B).
 
class predicates
    inc : (integer X) -> integer R.
clauses
    inc(X) = X+1.

Where the clause (X) = X+1 can be found in the last line. I.e. this time in a named predicate.

Variables that are bound outside (i.e. before the occurrence of) an anonymous predicate can be used inside the anonymous predicate. The value of variable will be captured by the anonymous predicate.

Variables that are bound in an anonymous predicate are local variables in the anonymous predicate.

Capturing context

An anonymous predicate can capture context, which means that it can refer to things that are defined in its context, especially facts and variables from the clause.

Capturing Variables

Anonymous predicate occurs in a clause, and this clause may contain variables. Those variables that are bound before the anonymous predicate is met can be used inside the anonymous predicate. This code illustrates how a variable is captured:

domains
    pred = (integer) -> integer.
 
class predicates
    createAdder : (integer A) -> pred Adder.
clauses
    createAdder(A) = { (X) = X+A }.
 
clauses
    run() :-
        Add17 = createAdder(17),
        A = Add17(4),
        B = Add17(20),
        stdio::writef("A = %, B = %", A, B).

We call createAdder with 17 as argument. So in the createAdder clause A is 17, and therefore the result is { (X) = X+17 }. We say that the anonymous predicate has captured the variable A.

Since Add17 is a predicate that adds 17 to its argument, the output of the code will be:

A = 21, B = 37


Capturing ellipsis (...)

An anonymous predicate can capture the ellipsis variable (i.e. ...):

clauses
    ppp(...)  :-
        W = { () :- stdio::write(...) },
        qqq(W).

W captures the ellipsis variable. qqq receives a zero-arity predicate, when this predicate is invoked the captured ellipsis variable will be written to the standard output device.

Capturing Facts

An anonymous predicate can access facts. If it is created by a class predicate it can access class facts. If it is created by an object predicate it can access both object and class facts. Consider this code that captures a class fact:

class facts
    count : integer := 0.
clauses
    seq() = { () = count :- count := count+1 }.
clauses
    run() :-
        A = seq(),
        B = seq(),
        stdio::writef("A1 = %, ", A()),
        stdio::writef("B1 = %, ", B()),
        stdio::writef("A2 = %, ", A()),
        stdio::writef("B2 = %", B()).

Both A and B increment the class fact count, so the result is

A1 = 1, B1 = 2, A2 = 3, B2 = 4

In object predicates we can capture object facts. So assuming that seq is an object predicate in myClass, this code illustrates the capture of an object fact:

facts
    count : integer := 0.
clauses
    seq() = { () = count :- count := count+1 }.
clauses
    run() :-
        A = myClass::new():seq(),
        B = myClass::new():seq(),
        stdio::writef("A1 = %, ", A()),
        stdio::writef("B1 = %, ", B()),
        stdio::writef("A2 = %, ", A()),
        stdio::writef("B2 = %", B()).

In this case A and B comes from two different objects, which each have a count fact, so the output will be:

A1 = 1, B1 = 1, A2 = 2, B2 = 2

Technically, the class version actually doesn't capture anything, it merely have access to the fact. Likewise, the object version doesn't actually capture the fact, instead it captures This and through This it obtains access to the object facts.

Capturing This

As described above it is possible to capture This and thereby gaining access to objects facts. The same mechanism gives access to calling object predicates.

clauses
    seq() = { () = count :- inc() }.
 
clauses
    inc() :- count := count+1.

This can also be used directly:

clauses
    ppp() = { () = aaa::rrr(This) }.
Nesting

Anonymous predicates can be nested:

clauses
    run() :-
        P = { (A) = { (B) = A+B } },
        Q = P(3300),
        R = P(2200),
        stdio::writef("Q(11) = %, ", Q(11)),
        stdio::writef("R(11) = %", R(11)).

To obtain Q we call P with 3300, so A is 3300 and Q therefore becomes { (B) = 3300+B } }, likewise R becomes { (B) = 2200+B } }. So, the output is:

Q(11) = 3311, R(11) = 2211


Syntactic Sugar

If you don't need the arguments they can be skipped. So this code-fragment:

P = { (_) :- succeed },
Q = { (_, _) = 0 },
R = { (_, _, _) = _ :- fail }
Can be shortened down to this:
P = { :- succeed },
Q = { = 0 },
R = { = _ :- fail }

Notice that the arguments are completely skipped. If you write () it means zero arguments, whereas skipping the arguments means "a suitable amount" of arguments.

Examples of practical usage

This section shows some cases where anonymous predicates are very handy. The examples assume that the PFC scopes core, std, stdio, list and string are open.

Dummy predicates

Anonymous predicates are good for creating dummy predicate values:

ppp( { = true } ), % don't filter (boolean)
qqq( { :- succeed } ), %  don't filter (determ)
rrr( { = 17 } ), % all rows must have height 17
Adaptation

In cases where you need a predicate and have one that is almost suitable, you can make the adaptation using an anonymous predicate.

Index adaptation

Consider the predicate write3:

class predicates
    write3 : (function{integer, string} Indexer).
clauses
    write3(Indexer) :-
        foreach I = std::fromTo(0,2) do
            write(Indexer(I), "\n")
        end foreach.

Indexer implements an "array" of strings, write3 will write the three strings found at the indexes 0, 1 and 2. So write3 assumes that the "array" index is zero-based. However, the "array" we have uses a one-based index:

class predicates
    myArray : (integer N) -> string Value.
clauses
    myArray(1) = "First" :- !.
    myArray(2) = "Second" :- !.
    myArray(3) = "Third" :- !.
    myArray(_) = _ :-
        raiseError().

But using an anonymous predicate we can easily adapt the one-based array to the zero-based usage:

% myArray is 0-based, write3 requires 1-based
Arr = { (N) = myArray(N+1) },
write3(Arr)

So we get the expected output:

First
Second
Third
Parameter adaptation

In this code listChildren will call a ChildWriter predicate for each "C is the child of P"-pair:

class predicates
    listChildren :
        (predicate{string,string} ChildWriter).
clauses
    listChildren(CW) :-
        CW("Son1", "Father"),
        CW("Son2", "Father").

We will however prefer to list the "P is the parent of C" using the predicate wParent:

class predicates
    wParent : (string Parent, string Child).
clauses
    wParent(P, C) :-
        writef("% is the parent of %\n", P, C).

wParent takes the arguments in the opposite order, but we can easily adapt using an anonymous predicate:

Swap = { (A,B) :- wParent(B,A) },
listChildren(Swap)

And then the out becomes the expected:

Father is the parent of Son1
Father is the parent of Son2

We can also throw away arguments, for example when calling this predicate that only needs a Child:

class predicates
    wKnowParent : (string Child).
clauses
    wKnowParent(C) :-
        writef("We know a parent of %\n", C).

The adaptation looks like this:

Fewer = { (C,P) :- wKnowParent(C) },
listChildren(Fewer)

The output will be:

We know a parent of Son1
We know a parent of Son2

We can also supply dummy arguments:

More = { (_,P) :- addChildren(P, 1) }
listChildren(More)

Here addChildren will "add a count of children to P". Since each invocation corresponds to one child we will call addChild supplying 1 as a "dummy" argument. The More is thus an adaptor that both throws away an argument and supplies a dummy argument.

Filters

Assume this predicate:

class predicates
    writeFiltered : 
        (string L, predicate_dt{integer} Filter).
clauses
    writeFiltered(Label, Filter) :-
        List = [1,2,3,4,5,6,7,8,9],
        FilteredList = filter(List, Filter),
        writef("%\t%\n", Label, FilteredList).

Filter is used to filter the list [1,2,3,4,5,6,7,8,9]; the filtered list and the Label are written to the standard output.

First we use the allow-all filter:

All = { :- succeed },
writeFiltered("All", All)

This filter simply succeeds for any element, so the output is the entire list:

All     [1,2,3,4,5,6,7,8,9]

It is just as easy to create a filter that fails for all elements and thus allow-none:

None = { :- fail },
writeFiltered("None", None)

The output from this is the empty list:

None    []

We can also create filters for elements greater than 3 and elements dividable by 3:

GreaterThan3 = { (X) :- X > 3 },
writeFiltered("> 3", GreaterThan3),
Rem3 = { (X) :- 0 = X rem 3 },
writeFiltered("Rem3", Rem3)

The output from this is:

> 3     [4,5,6,7,8,9]
Rem3    [3,6,9]
Sorting

The list package has a sort predicate. But sometimes the default order is not what you need. Therefore the list package also has a predicate sortBy, which sorts the elements using a programmer defined compare operation. Let us first consider string sorting, using this predicate:

class predicates
    writeStringsSorted :
        (string Label, comparator{string} Comp).
clauses
    writeStringsSorted(Label, C) :-
        List = ["John Wayne", "Uma Thurman",
            "Harrison Ford", "Nicolas Cage",
            "Elizabeth Taylor", "Cary Grant",
            "Jerry Lewis", "Robert De Niro"],
        Sorted = sortBy(C, List),
        write(Label, "\n"),
        foreach S = list::getMember_nd(Sorted) do
            writef("    %\n", S)
        end foreach.

We can call the predicate with the "normal" comparator, and using an anonymous predicate we can easily sort it descending as well:

Normal = compare,
writeStringsSorted("Normal", Normal),
Descending = { (A,B) = compare(B,A) },
writeStringsSorted("Descending", Descending)

The output looks like this:

Normal
    Cary Grant
    Elizabeth Taylor
    Harrison Ford
    Jerry Lewis
    John Wayne
    Nicolas Cage
    Robert De Niro
    Uma Thurman
Descending
    Uma Thurman
    Robert De Niro
    Nicolas Cage
    John Wayne
    Jerry Lewis
    Harrison Ford
    Elizabeth Taylor
    Cary Grant

Let us also sort some more complex elements. Here a person has a first name and a last name, using this domain:

domains
    person = p(string First, string Last).

For the demonstration we will use this test predicate:

class predicates
    writePersonsSorted : 
        (string Label, comparator{person} Comparator).
clauses
    writePersonsSorted(Label, C) :-
        List = [p("John","Wayne"),
            p("Uma","Thurman"),
            p("Harrison","Ford"),
            p("Nicolas","Cage"),
            p("Elizabeth","Taylor"),
            p("Cary","Grant"),
            p("Jerry","Lewis"),
            p("Robert","De Niro")],
        Sorted = sortBy(C, List),
        write(Label, "\n"),
        foreach p(F,L) = list::getMember_nd(Sorted) do
            writef("    % %\n", F, L)
        end foreach.

Again we can sort using the normal and a descending comparator:

Normal = compare,
writePersonsSorted("Normal", Normal),
Descending = { (A,B) = compare(B,A) },
writePersonsSorted("Descending", Descending)

Since the compare predicate uses left-to-right lexicographic order on the p-functor, the result is the same as before:

Normal
    Cary Grant
    Elizabeth Taylor
    Harrison Ford
    Jerry Lewis
    John Wayne
    Nicolas Cage
    Robert De Niro
    Uma Thurman
Descending
    Uma Thurman
    Robert De Niro
    Nicolas Cage
    John Wayne
    Jerry Lewis
    Harrison Ford
    Elizabeth Taylor
    Cary Grant

But with the more complex domain we can create a comparator that will sort on last name:

LN = { (p(_,L1), p(_, L2)) = compare(L1,L2) },
writePersonsSorted("LastName", LN)

The result is what we expect:

LastName
    Nicolas Cage
    Robert De Niro
    Harrison Ford
    Cary Grant
    Jerry Lewis
    Elizabeth Taylor
    Uma Thurman
    John Wayne
Capturing context

As mentioned a very powerful feature of anonymous predicates is the ability to capture context. The examples in this section show some ways you can use this.

Background threads

The routine for starting a thread takes a null-ary predicate and runs it in the new thread. But you nearly always need to pass some input data to the job in the new thread. This is possible in several ways, but the absolutely simplest way is to use anonymous predicates. The project bgDemo from the Visual Prolog example collection (that can be installed from the IDE) use this method. The project has a form that can start a background job and display status information from the job in a jobControl that is added to the form. A background job is a predicate that will receive a jobLog, which it can use to report status and completion degree:

domains
    job = (jobLog Log).

A jobLog looks like this:

interface jobLog
 
properties
    completion : real (i).
 
properties
    status : string (i).
 
end interface jobLog

The job can report completion degree by setting the completion property (range 0 to 1). Likewise, the status property can be used to reflect the current status of the job.

The status and completion will be shown in the form together with a job name. A job is started by calling the form's addJob predicate:

clauses
    addJob(JobName, Job) :-
        JobCtrl = jobControl::new(This),
        JobCtrl:name := JobName,
        JobCtrl:show(),
        assert(jobCtrl_fact(JobCtrl)),
        arrange(),
        JobLog = jobLog::new(JobCtrl),
        Action = { :- Job(JobLog) },
        _ = thread::start(Action).

In this context it is the last three lines that are interesting. thread::start takes a null-ary predicate as argument, but a job is a predicate that takes a jobLog as argument. Therefore we create an anonymous predicate Action, which takes no arguments but invokes Job on the JobLog. The anonymous predicate has captured both Job and JobLog from the context, and subsequently both these values are transferred to the new thread even though this thread only receives a null-ary predicate. The jobs in the bgDemo project are merely dummy jobs that only manipulate their jobLog. One of them looks like this:

clauses
    job(Log, From, To) :-
        Log:status := "Step 1",
        foreach N1 = std::fromTo(From, To) do
            Log:completion :=
                (N1-From) / (To-From) / 2,
            programControl::sleep(3)
        end foreach,
        Log:status := "Step 2",
        foreach N2 = std::fromTo(From, To) do
            Log:completion :=
                (N2-From) / (To-From) / 2 + 0.5,
            programControl::sleep(3)
        end foreach,
        Log:status := "finished".

It has two loops which run from From to To and calculates the completion and sets it on the Log. It also sets the status text before, between and after the loops. You may notice that the job does not have the proper job type, because a proper job only has one argument (the jobLog), this job has three arguments. Again it is anonymous predicates that help us. The code that adds the jobs to the form looks like this:

predicates
    onFileNew : window::menuItemListener.
clauses
    onFileNew(_Source, _MenuTag) :-
        JF = jobForm::display(This),
        Job11 = {(L) :- job1::job(L, 1, 1000)},
        Job12 = {(L) :- job1::job(L, 200, 600)},
        Job13 = {(L) :- job1::job(L, 1200, 3000)},
        Job14 = {(L) :- job1::job(L, 1, 1000)},
        JF:addJob("job1.1", Job11),
        JF:addJob("job1.2", Job12),
        JF:addJob("job1.3", Job13),
        JF:addJob("job1.4", Job14),
        ...

In a more realistic program, it is most likely that From and To would not be constants, but rather parameters passed from some outer place. In that case these anonymous predicates would also capture variables from the context. The jobLog in the bgDemo illustrates one more usage of anonymous predicates. The jobLog pass the completion and the status information to a jobControl. The jobControl is a GUI control on the jobForm capable of doing a suitable rendering of the information. This however gives a synchronization problem, because GUI controls are not thread safe and here we want to update some controls from a background thread. This can lead to conflicts, because it is the main thread that draws the controls. The solution is to make transfer the the update of the control to the GUI thread. We do this by posting actions to the control. The implementation of the status update looks like this:

clauses
    status(Status) :-
        Action = { :- jobCtrl:status := Status },
        jobCtrl:postAction(Action).

Action is a null-ary predicate that will set the status in the jobCtrl. We post this action to the jobCtrl. When the jobCtrl receives the action it invokes it and is thus updated. This way, the actual update of the control will be performed by the GUI thread. This anonymous predicate not only captures the Status variable it also captures the jobCtrl fact.

Asynchronous callbacks

Assume that we send commands to a remote service. The command execution is asynchronous, so when we execute a command we also give a callback action which will be invoked when the execution of the command is finished. To execute a command we must call this predicate:

predicates
    executeCommand :
        (command Cmd, predicate{} OnDone).

Based on this predicate we want to create a similar predicate that can execute a list of commands. A certain command should be executed when the previous command completes. We will also make our list executor asynchronous, so we supply an action that will be invoked when the entire script of commands are finished. Our script executer will have the form:

predicates
    executeScript :
        (command* Script, predicate{} OnDone).

If the script is empty we simply invoke the OnDone action. If the script has a command H and a rest script T, we must first execute H, and when it is finished we must execute the rest of the script T. So the OnDone action we supply when executing H must execute T. All in all, the implementation can look like this:

clauses
    executeScript([], OnDone) :-
        OnDone().
    executeScript([H|T], OnDone) :-
        DoneH = { :- executeScript(T, OnDone) },
        executeCommand(H, DoneH).

We have used an anonymous predicate to perform the execution of the rest of the script. This anonymous predicate captures T and OnDone.

Object Member Access

Whenever we have a reference to an object, we can access the object member predicates of that object.

MemberAccess:
    Term : Identifier

(Currently, the term must be a variable or a fact variable).

The identifier must have the type of the term.

Inside an implementation object member predicates can be invoked without reference to an object, because "This" is subsumed, see Scoping.

Object Expressions

An object expressions is an expressions that evaluates to an object. Like anonymous predicates it can capture values from and access facts in the context it appears in.

The syntax is like a regular class implementation without a name, but with a construction interface:

Example
Object = 
    implement : observer
      clauses
        onNext(A) :- F(toString(A)).
        onCompletion() :- F("\n").
    end implement

the implement ... end implement part is an object expression. To make object expressions a lightweight construction the keyword clauses is implicit/optional at the beginning of the scope (unless the scope has open, supports or inherits qualifications).

ObjectExpression: one of
    FullObjectExpression
    SimpleObjectExpression
 
FullObjectExpression:
    implement : ConstructionType
        ScopeQualifications
        Sections
    end implement
 
SimpleObjectExpression:
    implement : ConstructionType
        Clause-dot-term-list-opt
        Sections
    end implement

There will be exactly one constructor new\0. Like for other objects you don't need to supply an implementation if the constructor is 'trivial'.

inherits, supports, open, delegate and resolve works in the same way as in regular implementations.

Scoping

The predicates can refer to the context like anonymous predicates but can also refer to facts and inherited classes inside the object.

Object expressions are nested within other scopes, these scopes are all visible from the object expression. If a name is defined in more than one surrounding scope then the reference is to the closest level. Names from named scopes can be resolved with the scope name, but names from surrounding object expressions must be resolved by means of a variable as described in the "This" section.

This

In a predicate in an object expression the variable This represent the object of the object expression. There are no predefined names for surrounding This variables. So if you want to refer to an outer This you will have to put it in another variable e.g. Outer = This and then use that variable inside the object expression.

...
    Outer = This,
    Object = 
        implement : observer
            onNext(A) :- F(Outer, toString(A))).
            onCompletion() :- F(Outer, "\n")).
        end implement

Polymorphism (limitation)

Occasionally there will be situations where we will need a type variable. An example would be a predicate p : () -> observer{A} implemented using an object expression. Unfortunately, this is not possible until we get access to the type variables of polymorphic constructions.

clauses
    p(Value) = 
        implement : observer{A} % A is unknown/unbound
        facts
            v : A = Value. % A is unknown/unbound
        clauses
            onNext(V) :- v := V.
            onCompleted():- write(v).
        end implement.

Examples

The examples below will be based on iterator objets.

interface iterator
predicates
   hasNext : () determ.
   next : () -> integer.
end interface iterator

Which we will write using this predicate:

class predicates
    writeAll : (iterator It).
clauses
    writeAll(It) :-
        if It:hasNext() then
            writef("%\n", It:next()),
            writeAll(It)
        else
            write("<<<end>>>\n")
        end if.


Simple object

Example We can write a simple "null" iterator that is finished immediately.
class predicates
    test1 : ().
clauses
    test1() :-
        It =
            implement : iterator
                hasNext() :-
                    fail.
                next() = _ :-
                    exception::raise_error().
            end implement,
        writeAll(It).

Calling test1 will produce

<<<end>>>

Own state

Example In this example we create an iterator object that har its own fact n which is used to count down from 3
class predicates
    test2 : ().
clauses
    test2() :-
        It =
            implement : iterator
                hasNext() :-
                    n > 0.
                next() = N :-
                    N = n,
                    n := n - 1.
            facts
                n : integer := 3.
            end implement,
        writeAll(It).

Calling test2 will produce

 3
 2
 1
<<<end>>>

Capturing values

Example The count down object from above can be placed in its own predicate, and we can capture the value to count down From in the context:
class predicates
    countDown : (integer From) -> iterator It.
clauses
    countDown(From) =
        implement : iterator
            hasNext() :-
                n > 0.
            next() = N :-
                N = n,
                n := n - 1.
        facts
            n : integer := From.
        end implement.
 
class predicates
    test3 : ().
clauses
    test3() :-
        It = countDown(2),
        writeAll(It).

Calling test3 will produce

 2
 1
<<<end>>>


Example Here map create an iterator that maps the values returned iterator just like it would have modified the values of a list. Notice how the object expression captures the variables F and It and accesses them in its member predicates.
class predicates
    map : (function{integer, integer} Function, iterator It) -> iterator Mapped.
clauses
    map(F, It) =
        implement : iterator
            hasNext() :-
                It:hasNext().
            next() = F(It:next()).
        end implement.
 
class predicates
    test4 : ().
clauses
    test4() :-
        It1 = countDown(2),
        It2 = map({ (V) = V + 7 }, It1),
        writeAll(It2).

Calling test4 will produce

 9
 8
<<<end>>>

Capturing a fact

Example Here the fact count from the context is captured and manipulated from within the object expression.
class facts
    count : integer := 3.
 
class predicates
    test5 : ().
clauses
    test5() :-
        It =
            implement : iterator
                hasNext() :-
                    count > 0.
                next() = N :-
                    N = count,
                    count := count - 1.
            end implement,
        writeAll(It).

Calling test5 will produce

 3
 2
 1
<<<end>>>

inherits

Example Suppose we have a class randomIterator which generates random numbers and supports the iterator iterator.

We can inherit from this iterator to create a iterator that "throws a dice":

class predicates
    test6 : ().
clauses
    test6() :-
        Dice =
            implement : iterator inherits randomIterator
            clauses
                next() = randomIterator::next() mod 6 + 1.
            end implement,
        foreach _ = std::cIterate(5) do
            writef("Dice : %\n", Dice:next())
        end foreach.

By inheriting randomIterator we get the implementation of hasNext and only need to implement next.

Notice that it is necessary to use the clauses keyword in this case because the class has an inherits qualification.

Assuming that randomIterator will never end, we just iterate 5 times.

Calling test5 will produce (something like):

Dice : 1
Dice : 2
Dice : 4
Dice : 2
Dice : 3

Domain, Functor, and Constant Access

Domains, functors, and constants are all accessed as if they are class members. Even if they are declared in an interface. This means that when they are qualified, then they are always qualified with class/interface name and a double colon.

foreach

ForeachTerm :
  foreach Term do Term end foreach

Consider the schematic foreach term:

foreach Gen do Body end foreach

Gen is (typically) a nondeterm term. Body is evaluated for each solution of Gen. If/when Gen fails the foreach-term succeeds without evaluating Body. Body must be procedure (or erroneous). Gen and Body are both cut scopes.

The schematic foreach term resembles a fail loop:

Gen,
    Body,
fail

The main (and important) difference is that a foreach-term succeeds after the iteration, whereas a fail loop fails. As a result foreach-terms can be followed by other terms and they can be properly nested.

Example
foreach L = list::getMember_nd(LL) do
   write("<< "),
   foreach  X = list::getMember_nd(L) do
       write(" ", X)
   end foreach,
   write(" >>\n")
end foreach.

LL is supposed to be a list of lists. The outer foreach-loop iterates L through this list, so each L is a list. The inner foreach-loop iterates X through the elements of L.

There are a number of things to notice:

  • There is no comma before the keywords "do" and "end foreach".
  • The Body of a foreach-term is a cut scope, so a cut meet in the body will not influence the iteration.
  • A foreach-term always succeeds (or raises an exception), and no extra variables are bound after the evaluation of a foreach-term. Therefore, a foreach-term can only be used for its side effects.
Example
clauses
   p(L) :-
       foreach X = list::getMember_nd(L) do
           stdio::write(X, "\n")
       end foreach,
       stdio::write("Finished\n").

Foreach can be used instead of traditional "fail-loops":

clauses
   p(L) :-
       X = list::getMember_nd(L),
           stdio::write(X, "\n"),
       fail.
   p(_L) :-
       stdio::write("Finished\n").

In this context it is advantageous that Body must be a procedure, because in a "fail-loop" the body may accidentally fail before reaching the fail in the loop. Another advantage is that a foreach loop succeeds when the loop is finished, whereas a fail loop fails, so that execution can continue in the same clause. Also notice that foreach can be properly nested:

clauses
   p(LL) :-
       foreach L = list::getMember_nd(LL) do
           foreach X = list::getMember_nd(L) do 
               stdio::write(X, "\n")
           end foreach, 
           stdio::write("Finished a list\n")
       end foreach,
       stdio::write("Finished all lists\n").

if-then-else

if-then-else can be used both as a statement and as an expression.

if-then-else (statement)

The if-then-eslse statement conditionally executes a group of statements.

IfThenElseTerm:
    if Condition then Term Elseif-list-opt Else-opt end if
 
Elseif:
    elseif Condition then Term
 
Else:
    else Term

The following two terms are equivalents.

if Cond1 then T1 elseif Cond2 then T2 else T3 end if
if Cond1 then T1 else
  if Cond2 then T2 else T3 end if
end if

Consider the schematic if then else term:

if Cond then T1 else T2 end if

First Cond is evaluated, if it succeeds then T1 is evaluated otherwise T2 is evaluated.

Cond is followed by an implicit cut, which turns:

  • a nondeterm condition into a determ condition and
  • a multi condition into a procedure.

Cond is a cut-scope (see Cut Scopes).

Example
clauses
    w(X) :-
        if X div 2 = 0 and X > 3 then 
            write("X is good")
        else
            write("X is bad"),
            nl
        end if,
        nl.

There are several things to notice about this example:

  • You can use "and" and "or" logical operators and other "complex" terms in all three sub-terms.
  • There is no comma before the keywords "then", "elseif", "else", and "end if".

For readability sake, we always recommend using "or" instead of ";". Likewise we also recommend using "and" (instead of ",") when it (as in the condition above) represents a logical "condition" rather than a "sequentation".

Leaving out the else-part is just shorthand for writing that else succeed, i.e.

if Cond then Term end if

is short-hand for

if Cond then Term else succeed end if

if-then-else (expression)

The if-then-else expression conditionally evaluates expressions.

Syntactically it is same as the if-then-else statement, but and the terms in the branches must be expressions and the entire if-then-else expression will itself evaluate to a value.

The shorthand writings that leave out the else-part does not make sense for the expression.

Example
clauses
    w(X, Y) :-
        Min = if X < Y then X else Y end if,
        writef("The minimum is : %\n", Min).
The if-then-else expression above evaluates to X if X is less than Y else it evaluates to Y. Min is bound to the resulting value.


try-catch-finally

The try-catch-finally statement provides means for dealing with exceptions that may occur in a given block of code.

TryCatchTerm:
    try Term CatchFinally-list end try
 
CatchFinally: one of
    catch Variable do Trap-Handler
    finally Finally-Handler
 
Handler:
    Term

A try-construction thus have a Term and a list of catch and finally handlers.

The Term and the handlers are not allowed to leave backtrack points (i.e. they cannot have mode multi or nondeterm).

A try-construction with more than one handler is equivalent to nesting several try-constructions with one handler each. I.e. the following term:

try
   Body
catch Var1 do
   Handler1
finally
   Handler2
catch Var2 do
    Handler3
end try

Is equivalent to these nested terms:

try
    try
        try
            Body
        catch Var1 do
            Handler1
        end try
    finally
        Handler2
    end try
catch Var2 do
    Handler3
end try

try-catch

Consider the try-catch construction

try
    Body
catch Var do
    Handler
end try

First Body is evaluated.

If Body fails or succeeds the whole try construction fails or succeeds, respectively. I.e. if Body does not terminate with an exception the try construction corresponds to evaluating Body.

If Body terminates with an exception, then the exception is caught by the catch handler. Meaning that first Var is bound to the exception (in PFC context the exception will be a traceId) and then Handler is evaluated. In this case the construction behaves as if Handler is evaluated with Var bound to the caught exception.

Notice, that no bindings are made by Body if it terminates with an exception.

Example Handle the situation where a file cannot be read:
clauses
    read(Filename) = Contents :-
        try
            Contents = file::readFile(Filename)
        catch TraceId do
            stdio::writef("Could not read the file: %\n", Filename),
            exceptionDump::dumpToStdio(TraceId),
            Contents = ""
        end try.

First Contents = file::readFile(Filename) is evaluated, if that does not terminate with an exception read returns the contents of the file.

If it raises an exception the handler is evaluated with TraceId bound to the exception. So in this case a message is written to stdio and then the exception is dumped to stdio, and finally Contents is set to the empty string which is returned as result.

try-finally

Consider the try-finally construction:

try
    Body
finally
    Handler
end try

The purpose of the construction is to evaluate the Handler after the Body no matter how the Body terminates, i.e. whether it succeeds, fails or terminates with an exception (it cannot leave backtrack points).

The evaluation is like this

First Body is evaluated.

  • If Body succeeds: Handler is executed and the try-finally construction succeeds.
  • If Body fails: Handler is executed and the try-finally construction fails.
  • If Body terminates with an exception Exn: Handler is executed and the try-finally construction terminates with the exception Exn.
Example Ensure that an odbcStatement is free'd after use:
clauses
    tryGetName(Connection, PersonId) = Name :-
        Stmt = odbcStatement::new(Connection),
        try
            Stmt:setParameterValue(1, core::integer(PersonId)),
            Stmt:execDirect("select name from person where id=?"),
            Stmt:fetch(),
            Name = Stmt:getParameterValue_string(1)
        finally
            Stmt:free()
        end try.

The Stmt is free'd also if fetch fails, or if something terminates with an exception.