Primitives for Ambiguous Non-Deterministic Computation

AMB is the ambiguous special form for non-deterministic computation.

usage

(import :std/amb)

Special Forms

begin-amb

(begin-amb body ...)

Evaluates body in a fresh amb scope; you should always wrap the beginning of ambiguous computation in a begin-amb form to avoid side-effects leaking between amb executions.

begin-amb-random

(begin-amb-random body ...)

Like begin-amb, but the search strategy for generating amb values is randomized.

amb

(amb expr ...)

The ambiguous operator; may evaluate and return the value of any expression operand.

The order with which the values are generated depends on the search strategy. After v0.16-56-g6fb422de by default it is deterministic, unless the computation is within a begin-amb-random scope, in which case it is randomized. Prior to v0.16-56-g6fb422de the search strategy was always randomized.

amb-find

(amb-find expr [failure]) -> any

Evaluates expr returning its value if successful, possibly after backtracking. If the expression tree is exhausted, then failure is evaluated for the result; if failure is not specified, then an error is raised.

one-of

(one-of expr)

Same as (amb-find expr)

amb-collect

(amb-collect expr) -> list

Evaluates expr and performs backtracking repeatedly, collecting all possible values in a list.

all-of

(all-of expr) -> list

Same as (amb-collect expr).

amb-assert

(amb-assert expr)

Evaluates expr, failing if it is #f.

required

(required expr)

Same as (amb-assert expr)

amb-do

(amb-do thunks) -> any
  thunks := list of thunk

Procedural form of amb

amb-do-find

(amb-do-find thunk [failure]) -> any
  thunk, failure := thunk

Procedural form of amb-find

amb-do-collect

(amb-do-collect thunk) -> list

Procedural form of amb-collect

amb-exhausted?

(amb-exhausted? e) -> boolean

Predicate that returns true if e is an exception raised because the amb search was exhausted.

Example

Here is the well known dwelling house puzzle:

(def (solve-dwelling-puzzle)
  (begin-amb
   (let ((baker (amb 1 2 3 4 5))
         (cooper (amb 1 2 3 4 5))
         (fletcher (amb 1 2 3 4 5))
         (miller (amb 1 2 3 4 5))
         (smith (amb 1 2 3 4 5)))

     ;; They live on different floors.
     (required (distinct? (list baker cooper fletcher miller smith)))

     ;; Baker does not live on the top floor.
     (required (not (= baker 5)))

     ;; Cooper does not live on the bottom floor.
     (required (not (= cooper 1)))

     ;; Fletcher does not live on either the top or the bottom floor.
     (required (not (= fletcher 5)))
     (required (not (= fletcher 1)))

     ;; Miller lives on a higher floor than does Cooper.
     (required (> miller cooper))

     ;; Smith does not live on a floor adjacent to Fletcher's.
     (required (not (= (abs (- smith fletcher)) 1)))

     ;; Fletcher does not live on a floor adjacent to Cooper's.
     (required (not (= (abs (- fletcher cooper)) 1)))

     `((baker ,baker) (cooper ,cooper) (fletcher ,fletcher) (miller ,miller) (smith ,smith)))))

(def (distinct? lst)
  (let lp ((rest lst))
    (match rest
      ([hd . rest]
       (and (not (memq hd rest))
            (lp rest)))
      (else #t))))

(solve-dwelling-puzzle) => '((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1))