Non-deterministic predicates are defined using PREDICATE_NONDET(plname, cname, arity) or NAMED_PREDICATE_NONDET(plname, cname, arity).
A non-deterministic predicate returns a “context” , which
is passed to a subsequent retry. Typically, this context is allocated on
the first call to the predicate and freed when the predicate either
fails or does its last successful return (the context is nullptr
on the first call). To simplify this, a template helper function
PlControl::context_unique_ptr<ContextType>() provides a “smart
pointer” that frees the context on normal return or an exception;
when used with PL_retry_address(), the context's std:unique_ptr<ContextType>::release()
is used to pass the context to Prolog for the next retry, and to prevent
the context from being freed. If the predicate is called with PL_PRUNE,
the normal return true will implicitly free the context.
The skeleton for a typical non-deterministic predicate is as follows.
The test for PL_PRUNED is done first to avoid an unneeded
PlFrame and also to ensure that A1, A2,
etc. aren't used when they have the value
PlTerm::null.31This
code could be structured as a switch statement, but
typically the PL_FIRST_CALL case falls through to the PL_REDO
case. There are a number of examples of non-deterministic
predicates in the test code test_cpp.cpp.
struct PredContext { ... }; // The "context" for retries
PREDICATE_NONDET(pred, <arity>)
{ // "ctxt" must be acquired so that the destructor deletes it
auto ctxt = handle.context_unique_ptr<PredContext>();
const auto control = handle.foreign_control();
if ( control == PL_PRUNED )
return true;
// Can use A1, A2, etc. after we know control != PL_PRUNED
if ( ... ) // deterministic result
{ assert(control == PL_FIRST_CALL);
if ( ... )
return true; // Success (and no more solutions)
else
return fase;
}
if ( control = PL_FIRST_CALL )
{ ctxt.reset(new PredContext(...));
...
} else
{ assert(control == PL_REDO);
}
PlFrame fr;
for ( ; ctxt->valid(...) ; ctxt->next() )
{ if ( ... unify a result ... )
{ ctxt->next();
if ( ctxt->valid(...) )
PL_retry_addresss(ctxt.release()); // Succeed with a choice point
else
return true; // deterministic success
}
fr.rewind();
}
return false;
}