Lessons/Succeed, fail and backtrack - part 3
Here we will consider more ways to use the knowledge in our little "knowledge base" (i.e. from Succeed, fail and backtrack - part 2).
clauses runGoal() :- father(Person, Father), stdio::writef("% is the father of %.\n", Father, Person).
This clause will call father with two free variables, i.e. both arguments are output arguments. Therefore it will succeed for the first clause and have created a backtrack point to the second clause. And so forth.
Notice that the actual backtracking takes place from runGoal to father.
clauses runGoal() :- foreach father(Person, Father) do stdio::writef("% is the father of %.\n", Father, Person) end foreach, stdio::write("That is all we know about fathers\n").
foreach <P> do <Q> end foreach is a construction that executes <P> and if it succeeds then it executes <Q> after <Q> has executed it will backtrack into <P> if that is possible and if <P> succeeds again then <Q> is also run again, this continues until <P> finally fails, at which point the execution will continue after end foreach.
fail is a built-in predicate that always fails, it can be used to achieve a similar behavior as the one foreach achieves:
clauses runGoal() :- father(Person, Father), stdio::writef("% is the father of %.\n", Father, Person), fail. runGoal() :- stdio::write("That is all we know about fathers\n").
After we have found a Person and its Father and written it we call fail. And since fail fails we will backtrack:
- Into father if there is a backtrack point there.
- And otherwise to the second runGoal clause.
So besides the syntactic difference in using foreach and fail there is also the difference that after having found all fathers, the foreach construction will succeed and execution will therefore continue after end foreach in the same clause; fail on the other hand fails, so in after having found all fathers fail will fail, and we will therefore the entire clause containing fail will itself fail, and execution will therefore continue in the next clause.
Summary:
- We can use the "knowledge base" with different combinations of bound and free arguments.
- foreach can be used to exhaust backtracking of a certain "generator" and perform some action of each "generated thing".
- when the backtracking in a foreach construction is finally exhausted, the entire foreach construction succeeds.
- fail is a built-in predicate that fails when called.
clauses runGoal() :- Person = "Sue", if father(Person, Father) then stdio::writef("% is the father of %.\n", Father, Person) else stdio::writef("We don't kno who the father of % is.\n", Person) end if, stdio::write("<<end>>\n").
if <C> then <P> else <Q> end if is a construction that will first execute <C> if that succeeds it will execute <P> if it fails it will execute <Q> instead. Backtrack points in <C> (if any) will be discarded, so there will be no backtracking into <C>. So <C> is used to choose between <P> and <Q>. If (let us say) <P> is chosen then the entire if construction functions as if <P> was called. So if <P> fails then the entire if construction fails, and it <P> succeeds then the entire if construction succeeds. Finally, if <P> created backtrack points then backtracking will return into <P>.
If you don't want anything to happen in the then-part you can use the built-in succeed predicate, which simply succeeds, or you can write nothing at all:
if <C> then succeed else <Q> end if if <C> then else <Q> end if
If you don't want anything to take place in the else part you can also leave out the word else, so these three things have the same meaning:
if <C> then <P> else succeed end if if <C> then <P> else end if if <C> then <P> end if
Summary
- succeed is a build-in predicate that succeeds
- if <C> then <P> else <Q> end if runs C to choose between <P> and <Q>.