Skip to content

Then and Back

Bill Hails edited this page Dec 13, 2023 · 8 revisions

Chronological Backtracking means exactly that: winding back the clock to a previous stage in the computation. It has numerous applications, particularly in complex problem-solving where the problem space must be searched.

The language supports chronological backtracking with two keywords: then and back. then behaves syntactically as a right associative binary operator. It evaluates and returns its lhs, but if backtracked to, it evaluates and returns its rhs. The keyword back causes immediate backtracking. Here's an example from the tests:

fn require(condition) {
    condition or back
}

fn one_of {
    ([]) { back }
    ( h @ t) { h then one_of(t) }
}
                
{
    define x = one_of([1, 2, 3, 4, 5]);
    require(x == 4);
    x; // 4
}

The function one_of is defined to first return the head of its argument list, then if backtracked to it recurses on the tail of the list.

The unnamed body then defines x to be one_of the numbers 1, 2, 3, 4 or 5, then requires that x be 4 (which repeatedly backtracks into one_of until one_of returns 4) then the require succeeds and the expression produces x which is now 4.

Here's a more involved example, generating pythagorean triples where x squared plus y squared equals z squared.

fn square(x) { x * x }

fn integers_from(n) {
   n then integers_from(n + 1)
}

fn integers_between(lower, upper) {
    require(lower <= upper);
    lower then integers_between(lower + 1, upper);
}

fn pythagorean_triples() {
    z = integers_from(1);
    x = integers_between(1, z);
    y = integers_between(x, z);
    require(square(x) + square(y) == square(z));
    [x, y, z];
}

The first call to pythagorean_triples returns the list [3, 4, 5], but if repeatedly backtracked through will continue to produce all pythagorean triples. Note that then does not evaluate its rhs until it is backtracked through, so integers_from is not a runaway recursion.

It should be pointed out that chronological backtracking is far more than some kind of try/throw/catch. In some sense it is like try/throw/catch but unlike try/catch the then can be done at any chronologically previous point in the process, not just below some stack-based scope. A back will backtrack to the most recently executed then anywhere in the code.

Under the hood this is an implementation of the construct amb from the classic SICP (Ch 4, S 3)

Clone this wiki locally