Starting from some initial value, a backtrack program incrementally constructs a solution by extending the current partial solution with some legal choice. If the partial solution becomes a complete solution it is returned. Otherwise, upon reaching a dead end, the program backtracks to the previous choice point, discards the choice it made there, and continues as though that choice were illegal in the first place. It may happen that every possible choice is examined and rejected, in which case the problem has no solution.
In Scheme we can write a general backtracker, parameterized by the specifics of the problem being solved:
These nine parameters define the problem space as follows:(solve *initial* *complete?* *extensions* *first* *rest* *empty?* *extend* *legal?* *return*)
(*initial*) returns an initial partial solution (psol) (*complete?* psol) true if partial solution psol is a complete solution (*extensions* psol) creates the set of all possible choices to extend psol (*first* choices) selects first of our choices (*rest* choices) returns remaining choices (*empty?* choices) true if all choices are exhausted (*extend* psol choice) extends partial solution by given choice (*legal?* psol) true if psol is a legal partial solution (*return* sol) returns the (complete) solution sol
Here is the code for solve. Look specifically at how the two continuations are used. The success continuation is k and the failure continuation is q. Notice that k takes a parameter (what to do with a solution) and q needs no parameter.
(define solve (lambda (*initial* *complete?* *extensions* *first* *rest* *empty?* *extend* *legal?* *return*) (letrec ([try (lambda (psol choices k q) (if (*complete?* psol) (k psol) ; finished, so apply success k (if (*empty?* choices) (q) ; exhausted all choices, so backtrack (let ([new-psol (*extend* psol (*first* choices))]) (if (*legal?* new-psol) (try new-psol (*extensions* new-psol) k (lambda () (try psol (*rest* choices) k q))) (try psol (*rest* choices) k q))))))]) (try *initial* (*extensions* *initial*) *return* (lambda () 'failed)))))1
The success continuation k is used as in CPS to carry the computation forward and return the answer. The failure continuation q is invoked to backtrack when the choice list becomes empty. This is why a new q is constructed in the call (try new-psol ...), in which we have extended the current partial solution and are moving forward to extend the next with a recursive call. The new failure continuation backtracks with a call to try with the current partial solution, starting from the next choice.