The previous sections covered the creation of deterministic external predicates. The nondeterministic predicates can of course use the same macros as the deterministic ones, however their functionality is larger in that they are sensitive to the backtracking process.
A non deterministic Prolog procedure has potentially more than one solution. When it is first called, a solution is found if possible, and execution proceeds. If a subsequent goal fails, backtracking occurs, and an attempt is made to find another solution to the call of the procedure. To make this action possible in the procedural language C, ECLiPSe
provides a macro which will take note of the values to be given to the arguments of an external procedure if an attempt is made to resatisfy it. This macro is Remember().
To illustrate its use, consider the next example, it shows a C implementation of the Prolog predicate member/2. As opposed to the well-known Prolog definition this one does not work backwards, i.e. it will not construct lists when called with the second argument uninstantiated:
To link this C function to a Prolog predicate use b_external/2 as follows:int p_member(velt, telt, vlist, tlist) value velt, vlist; type telt, tlist; { pword *p; /* we require a list or nil */ Check_List(tlist); /* if the list is empty, we fail */ if(IsNil(tlist)) { Cut_External; Fail; } /* the tail of the list */ p = vlist.ptr + 1; /* must be dereferenced! */ Dereference(p); /* * on backtracking we will get the tail of the list * instead of the list itself */ Remember(2, p->val, p->tag); /* * and we behave as the unification * of the element and the head */ Return_Unify_Pw(velt, telt, vlist.ptr->val, vlist.ptr->tag); }
In a Prolog program the callb_external(member/2, p_member)
will succeed if X is a member of the list List. This is clearly resatisfiable - for example in the callmember(X, List)
where X is an uninstantiated variable, X will first be instantiated to 1, and on resatisfaction of the goal it will successively be instantiated to 2, 3, and 4 before it finally fails. A solution to the callmember(X, [1, 2, 3, 4])
is found when X is unified with the head of List by the Return_Unify_Pw() statement in the last line of the function. When an attempt is made to resatisfy the above clause, X must be instantiated to another element of List. This will be done if the procedure is called again with a new instantiation of its arguments, namely that when the second argument has the value of the tail of the list which it was given on the previous call.member(X, List)
The use of the macro Remember() makes this new instantiation of the procedure's arguments possible. In an external procedure, the call of the macro
has the effect that if an attempt is made to resatisfy the procedure, the value given in argument n of the Prolog procedure will be the Prolog term represented by the value val and the tag tag. The type of val must be value and the type of tag must be type.Remember(n, val, tag);
Thus in the above example, it is required that the procedure be called with the second argument instantiated to the tail of the list which was its previous value. So a pointer is taken to the tail by the assignment of the pointer p in the fourth statement, and the call
informs ECLiPSeRemember(2, p->val, p->tag);
that to resatisfy the goal a call of the Prolog procedure must be made again, with the second argument instantiated to the object pointed to by p.
Note the use of the Dereference macro in the previous example. p is the tail of the list and so to access its value, it must be dereferenced, even though it will become predicate argument in the next invocation of p_member - only the terms which are arguments of the normal (not after backtracking) invocation are dereferenced by the system.
A nondeterministic external predicate is resatisfiable until it explicitly cuts all following alternatives using the macro Cut_External, i.e. if it uses Return_Unify and this unification fails or if it uses the macro Fail, it will be called immediately again with the newly remembered value, if any. If it is known that the current solution is the last one, the macro Succeed_Last can be used for a deterministic exit from this external predicate, i.e. the predicate succeeds and is no longer resatisfiable.
To perform the backtracking operation, some information must be stored between one attempt at satisfaction of the goal and an attempt to resatisfy it. By this mechanism, the backtracking of Prolog can be imitated by the facilities in C. It is not always possible (and seldom desirable) to map directly from the definition of the procedure in Prolog to an implementation in C. Some ingenuity may be required. A very simple example is the procedure p/1 defined in Prolog as
This is implemented as a C external by defining the Prolog codep(a). p(b). p(c).
The C external is then written for the procedure p/2, whose auxiliary second argument serves to count its invocations.p(X) :- p(X, 1).
int p_p2(v1, t1, v2, t2) value v1, v2; type t1, t2; { char *result; pword new; /* first check the arguments */ Check_Integer(t2); Check_Output_Atom(t1); /* take note of new resatisfaction */ Make_Integer(&new, v2.nint + 1); Remember(2, new.val, new.tag); /* get the string that corresponds to the value of v2 */ switch(v2.nint) { case 1: result = "a"; break; case 2: result = "b"; break; case 3: result = "c"; break; default: Fail; } Return_Unify_Atom(v1, t1, Did(result, 0)); }