Did you know ... | Search Documentation: |
The class PlFrame - Unification and foreign frames |
As documented with PL_unify(), if a unification call fails and
control isn't made immediately to Prolog, any changes made by
unification must be undone. The functions PL_open_foreign_frame(),
PL_rewind_foreign_frame(), PL_discard_foreign_frame(), and
PL_close_foreign_frame() are encapsulated in the class
PlFrame
, whose destructor calls
PL_close_foreign_frame(). Using this, the example code with PL_unify()
can be written:
PREDICATE(can_unify_ffi, 2) { fid_t fid = PL_open_foreign_frame(); int rval = PL_unify(A1.unwrap(), A2.unwrap()); PL_discard_foreign_frame(fid); return rval; }
/* equivalent to the Prolog code T1 = T2 -> do_one_thing ; do_another_thing */ { PlFrame fr; bool t1_t2_unified = A1.unify_term(A2); if ( ! t1_t2_unified ) fr.rewind(); if ( t1_t2_unified ) do_one_thing(...); else do_another_thing(...); }
The following is C++ version of the code example for PL_open_foreign_frame(). The calls to PL_close_foreign_frame() and the check for PL_exception(0) in the C code aren't needed in the C++ code:
static std::vector<std::string> lookup_unifies = { "item(one, 1)", "item(two, 2)", "item(three, 3)" }; PREDICATE(lookup_unify, 1) { PlFrame fr; for (auto& s : lookup_unifies ) { PlCompound t(s); if ( A1.unify_term(t) ) return true; fr.rewind(); } return false; }
or using this convenience wrapper:
if ( RewindOnFail([t1=A1,t2=A2]()->bool { return t1.unify_term(t2); }) ) do_one_thing(...); else do_another_thing(...);
Note that PlTerm::unify_term()
checks for an error and throws an exception to Prolog; if you wish to
handle exceptions, you must call PL_unify_term(t1. unwrap(),t2.
unwrap())
.
The class PlFrame
provides an interface to discard
unused term-references as well as rewinding unifications (data-backtracking).
Reclaiming unused term-references is automatically performed after a
call to a C++-defined predicate has finished and returns control to
Prolog. In this scenario PlFrame
is rarely of any use. This
class comes into play if the toplevel program is defined in C++ and
calls Prolog multiple times. Setting up arguments to a query requires
term-references and using PlFrame
is the only way to
reclaim them.
Another use of of PlFrame
is when multiple separate
unifications are done - if any of them fails, then the earlier
unifications must be undone before returning to Prolog.
PlFrame
.
A typical use for PlFrame
is
the definition of C++ functions that call Prolog and may be called
repeatedly from C++. Consider the definition of assertWord(),
adding a fact to word/1;
the PlFrame
removes the new term av[0]
from
the stack, which prevents the stack from growing each time assertWord()
is called:
void assertWord(const char *word) { PlFrame fr; PlTermv av(1); av[0] = PlCompound("word", PlTermv(word)); PlQuery q("assert", av); PlCheckFail(q.next_solution()); }
The following example uses PlFrame
in the context of a
foreign predicate. The can_unify/2’s
truth-value is the same as for Prolog unification (=/2), but has no side
effects. In Prolog one would use double negation to achieve this:
PREDICATE(can_unify, 2) { PlFrame fr; int rval = (A1=A2); fr.discard(); // or, less efficiently: fr.rewindd(); return rval; }
Here is an example of using PlRewindOnFail(),
where name_to_terms
contains a map from names to terms
(which are made global by using the PL_record() function). The
frame rewind is needed in the situation where the first unify_term()
succeeds and the second one fails.
static const std::map<const std::string, PlRecord> name_to_term = { {"a", PlTerm(...).record(), PlTerm(...).record()}, ... }; PREDICATE(name_to_terms, 3) { A1.must_be_atom_or_string(); const auto it = name_to_term.find(A1.as_string()); return it != name_to_term.cend() && PlRewindOnFail([t1=A2,t2=A3,&it]() { return t1.unify_term(it->second.first.term()) && t2.unify_term(it->second.second.term()); }); }
The equivalent code without using PlRewindOnFail() is:
PREDICATE(name_to_terms, 3) { PlTerm key(A1), term1(A2), term2(A3); const auto it = name_to_term.find(key.as_string()); if ( it == name_to_term.cend() ) return false; if ( !term1.unify_term(it->second.first.term()) ) return false; PlFrame fr; if ( !term2.unify_term(it->second.second.term()) ) { fr.discard(); return false; } return true; }