← back to programming

Nondeterministic Computing

Abelson & Sussman · 1996 · SICP §4.3

amb chooses a value that makes the rest of the computation succeed. Backtracking search is built into the evaluator. Logic puzzles become declarative programs.

amb A B backtrack amb chooses; failure backtracks to the last choice point

amb and search

(amb 1 2 3) "ambiguously" returns one of its arguments. If a later require fails, the evaluator backtracks and tries the next choice. This turns depth-first search into a language primitive. You describe what you want; the evaluator figures out how.

Scheme

Examples — logic puzzles

Nondeterministic programming shines on constraint-satisfaction problems. State the constraints; let backtracking find the solution. The classic example: who lives on which floor?

Scheme

Parsing as nondeterministic search

Parsing a sentence is choosing how to split it into valid parts. With amb, a parser can "guess" a split point and backtrack if the rest doesn't parse. This makes grammar rules look like their BNF definitions.

Scheme

Implementing the amb evaluator

The amb evaluator threads two continuations through every expression: a success continuation (what to do with a value) and a failure continuation (what to do when stuck). When amb runs out of choices, it calls the failure continuation, which backtracks to the previous choice point.

Scheme

Example — eight queens

Place 8 queens on a chessboard so no two attack each other. With amb, you'd write: choose a column for each row, require no conflicts. Here we use the same backtracking logic explicitly.

Scheme

Notation reference

Scheme (amb) Python Meaning
(amb 1 2 3)for x in [1,2,3]Choose one value nondeterministically
(require pred)if not pred: continueFail and backtrack if false
success continuationcallback(val)What to do with a value
failure continuationbacktrack / next()What to do when stuck
call/ccyield / generatorsCapture current continuation
Neighbors

Other reading pages

Foundations (Wikipedia)

Translation notes

Python has no built-in amb, but generators with yield provide a natural translation: each choice point becomes a for loop, and backtracking is implicit in the generator protocol. The continuation-passing style version (success/failure callbacks) maps directly to CPS in Python. For real constraint problems, Python's itertools.product with filtering is the pragmatic equivalent of nested amb calls.

Ready for the real thing? Read SICP §4.3.