nondet C code

Fergus Henderson fjh at cs.mu.oz.au
Mon Aug 11 04:45:20 AEST 1997


Zoltan, have you still got a copy of your proposal for nondet C code?
I have saved a copy of my alternative proposal, but I can't find your
original mail.

P.S.  Here's how Eclipse does it.

     _________________________________________________________________

                          Nondeterministic Externals
                                       
   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:
   
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);
}

   To link this C function to a Prolog predicate use b_external/2 as
   follows:
   
b_external(member/2, p_member)

   In a Prolog program the call
   
member(X, List)

   will succeed if X is a member of the list List. This is clearly
   resatisfiable - for example in the call
   
member(X, [1, 2, 3, 4])

   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
   call
   
member(X, List)

   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.
   
   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
   
Remember(n, val, tag);

   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.
   
   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
   
Remember(2, p->val, p->tag);

   informs ECLiPSe 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
   
p(a).
p(b).
p(c).

   This is implemented as a C external by defining the Prolog code
   
p(X) :- p(X, 1).

   The C external is then written for the procedure p/2, whose auxiliary
   second argument serves to count its invocations.
   
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));
}
     _________________________________________________________________
-- 
Fergus Henderson <fjh at cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3         |     -- the last words of T. S. Garp.



More information about the developers mailing list