amb: Ambiguous Operator
1 Ambiguous Operator
syntax
(amb expr ...)
The form (amb expr ...) expands to (amb* (λ () expr) ...).
> (amb 1 (values 2 3) 4) 1
> (amb)
2
3
> (amb) 4
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
Wrapping amb expressions with a new amb sequence is recommended. This ensures that each instance of non-deterministic computation starts with a fresh sequence, avoiding unintended interactions between different amb expressions.
> (define make-amb-tasks (current-amb-maker)) > (amb 1 (values 2 3) 4) 1
> (parameterize ([current-amb-tasks (make-amb-tasks)]) (let ([x (amb 1 2)]) (displayln (list x)) (let ([y (amb 3 4)]) (displayln (list x y)) (let ([z (amb 5 6)]) (displayln (list x y z)) (amb)))))
(1)
(1 3)
(1 3 5)
(1 3 6)
(1 4)
(1 4 5)
(1 4 6)
(2)
(2 3)
(2 3 5)
(2 3 6)
(2 4)
(2 4 5)
(2 4 6)
amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (amb)
2
3
> (parameterize ([current-amb-tasks (make-amb-tasks)] [current-amb-pusher enqueue!]) (let ([x (amb 1 2)]) (displayln (list x)) (let ([y (amb 3 4)]) (displayln (list x y)) (let ([z (amb 5 6)]) (displayln (list x y z)) (amb)))))
(1)
(2)
(1 3)
(1 4)
(2 3)
(2 4)
(1 3 5)
(1 3 6)
(1 4 5)
(1 4 6)
(2 3 5)
(2 3 6)
(2 4 5)
(2 4 6)
amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (amb) 4
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
For most use cases, we recommend using in-amb to construct a stream from ambiguous computations. A good practice is to wrap amb expressions in a procedure, then use in-amb to create a lazy stream that produces as many results as needed.
> (define (f n) (define m (amb 0 1 2 3 4)) (unless (> m n) (amb)) m) > (for/list ([m (in-amb (f 2))]) m) '(3 4)
If an alt is the amb* procedure itself, it is skipped, and evaluation proceeds to the next alt.
> (define (zero) 0) > (define (one) 1) > (current-amb-shuffler displayln) > (amb* amb* zero amb* one amb*) #(#<procedure:amb*> #<procedure:zero> #<procedure:amb*> #<procedure:one> #<procedure:amb*>)
0
> (amb) #()
1
> (amb) #()
amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (define (amb+) (amb)) > (amb* amb+ zero amb+ one amb+)
#(#<procedure:amb+> #<procedure:zero> #<procedure:amb+> #<procedure:one> #<procedure:amb+>)
#()
0
> (amb)
#()
#()
1
> (amb)
#()
#()
amb: empty amb tasks;
expected at least one amb task
in: (amb)
syntax
(for/amb maybe-length (for-clause ...) break-clause ... body ...+)
(for/amb type-ann-maybe maybe-length (for-clause ...) type-ann-maybe expr ...+)
syntax
(for*/amb maybe-length (for-clause ...) break-clause ... body ...+)
(for*/amb type-ann-maybe maybe-length (for-clause ...) type-ann-maybe expr ...+)
When the #:length clause is specified, the syntax resembles for/vector and for*/vector. The loop body for each iteration is wrapped in a thunk to create a fixed number of alternatives.
> (for/amb #:length 4 ([i #(1 2)] [j #(x y)]) (values i j))
1
'x
> (amb)
2
'y
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (for/amb #:length 4 #:fill 0 ([i #(1 2)] [j #(x y)]) (values i j))
1
'x
> (amb)
2
'y
> (amb) 0
> (amb) 0
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
If the #:length clause is omitted, a choice point is created for each iteration. After the body of one iteration is evaluated, backtracking via (amb) proceeds to the next iteration. This allows for iterating over sequences of any size, including infinite ones.
> (for/amb ([i (in-naturals)]) i) 0
> (amb) 1
> (amb) 2
> (amb) 3
2 Prompt Tag
3 Stream Constructor
syntax
(in-amb expr)
The form (in-amb expr) expands to (in-amb* (λ () expr)).
in-amb acts as a one-step lookahead sequence generator. To determine if the sequence continues, it must compute the next valid path.
> (for/list ([i (in-amb (begin0 (amb 1 (amb 2 3) 4) (displayln 'data)))] [_ 2]) i)
data
data
data
'(1 2)
> (for/list ([_ 2] [i (in-amb (begin0 (amb 1 (amb 2 3) 4) (displayln 'data)))]) i)
data
data
'(1 2)
The ambiguous computation is executed in the dynamic context where in-amb is called. In particular, dynamic bindings such as those introduced by parameterize are preserved from the stream’s creation site rather than from the site where the stream is consumed.
> (define p (make-parameter 0)) > (define s (parameterize ([p 1]) (in-amb (amb 0 (p) 2)))) > (for/list ([i (parameterize ([p 2]) s)]) i) '(0 1 2)
Internally, in-amb installs the parameters required to run an ambiguous computation, including current-amb-prompt-tag, current-amb-tasks, and current-amb-empty-handler. Other parameters retain the values they had when in-amb was invoked.
> (amb 1 2 3) 1
> (for/list ([(i j) (in-amb (amb (values 1 'x) (values 2 'y)))]) (cons i j)) '((1 . x) (2 . y))
> (amb) 2
> (define (rotate-queue! q) (when (non-empty-queue? q) (enqueue! q (dequeue! q))))
> (parameterize ([current-amb-rotator rotate-queue!]) (for/list ([i (in-amb (amb (amb 1 2 3) (amb 'a 'b 'c)))]) i)) '(1 a 2 b 3 c)
> (amb) 3
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (define (make) (parameterize ([current-amb-pusher enqueue!]) (let g () (amb 0 1 (cons (g) (g)))))) > (for/list ([x (in-amb* make)] [_ 10]) x) '(0 1 (0 . 0) (0 . 1) (1 . 0) (1 . 1) (0 0 . 0) (0 0 . 1) (0 1 . 0) (0 1 . 1))
syntax
(in-amb/do expr)
procedure
(in-amb*/do thk) → sequence?
thk : (-> any)
4 Exception Type
struct
(struct exn:fail:contract:amb exn:fail:contract () #:extra-constructor-name make-exn:fail:contract:amb #:transparent)
> (amb) amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (amb*) amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (for/amb ([i '()]) i) amb: empty amb tasks;
expected at least one amb task
in: (amb)
> (for*/amb ([i '()]) i) amb: empty amb tasks;
expected at least one amb task
in: (amb)
procedure
> (raise-amb-error) amb: empty amb tasks;
expected at least one amb task
in: (amb)
5 Parameter
The behavior of the amb search engine can be customized through a set of parameters. These parameters control how amb tasks are stored, scheduled, and executed, as well as the continuation prompt used to delimit ambiguous computations.
parameter
(current-amb-prompt-tag prompt-tag) → void? prompt-tag : continuation-prompt-tag?
parameter
(current-amb-empty-handler empty-handler) → void? empty-handler : (-> none/c)
parameter
(current-amb-shuffler shuffle!) → void? shuffle! : (-> mutable-vector? void?)
parameter
(current-amb-rotator) → (-> sequence? void?)
(current-amb-rotator rotate!) → void? rotate! : (-> sequence? void?)
parameter
(current-amb-maker) → (-> sequence?)
(current-amb-maker make) → void? make : (-> sequence?)
parameter
(current-amb-tasks tasks) → void? tasks : sequence?
parameter
→ (-> sequence? exact-nonnegative-integer?) (current-amb-length length) → void? length : (-> sequence? exact-nonnegative-integer?)
parameter
(current-amb-pusher push!) → void? push! : (-> sequence? continuation? void?)
parameter
(current-amb-popper pop!) → void? pop! : (-> sequence? continuation?)