For simplicity, I have omitted the environment argument and have substituted the data objects (in capital letters) for the variables that are bound to them. If the expressions are being evaluated at the top level of the read-eval-print loop the value of succeed is (lambda (val next-try) (micro-print val) (main-loop next-try)) and the value of fail is (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p)) which is also the value of next-try when succeed is called. *********************** (amb-eval 2 succeed fail) reduces to (succeed 2 fail) *********************** (amb-eval (+ 2) succeed fail) reduces to (amb-eval + (lambda (proc fail) (get-args (2) (lambda (args fail) (micro-apply proc args succeed fail)) fail)) fail) + evaluates to prim:+ (shorthand for (primitive +)), so the above reduces to (get-args (2) (lambda (args fail) (micro-apply prim:+ args succeed fail)) fail) This reduces to (amb-eval 2 (lambda (arg fail) (get-args () (lambda (args fail) ((lambda (args fail) (micro-apply prim:+ args succeed fail)) (cons arg args) fail)) fail)) fail) 2 evaluates to itself, so the above reduces to (get-args () (lambda (args fail) ((lambda (args fail) (micro-apply prim:+ args succeed fail)) (cons 2 args) fail)) fail) This reduces to ((lambda (args fail) (micro-apply prim:+ args succeed fail)) (2) fail) This reduces to (micro-apply prim:+ (2) succeed fail) Since prim:+ is a primitive function and 2 is the result of (apply + (2)), then the above reduces to (succeed 2 fail) ********************* To see how backtracking is accomplished, suppose that the top-level expression to be evaluated is (AMB 1 2). Main-loop prints out "new problem" and calls (amb-eval (AMB 1 2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))) This reduces to (micro-amb (1 2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))) This reduces to (amb-eval 1 (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (micro-amb (2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))))) Since 1 evaluates to itself, amb-eval applies (lambda (val next-try) (micro-print val) (main-loop next-try)) to val = 1 and next-try = (lambda () (micro-amb (2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p)))) Consequently, micro-print prints out 1 and then (main-loop next-try) executes, causing the prompt ambmicro> to be displayed. When the user types "try-again", main-loop calls (try-again), which in this instance is the procedure (lambda () (micro-amb (2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p)))) This reduces to (micro-amb (2) (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))) which reduces to (amb-eval 2 (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (micro-amb () (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))))) Again, 2 evaluates to itself, so amb-eval applies the succeed procedure to val = 2 and next-try = (lambda () (micro-amb () (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p)))) which in turn prints out 2 and calls main-loop with the argument try-again = (lambda () (micro-amb () (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p)))) The prompt "ambmicro> " is displayed. When the user types "try-again," main-loop calls (try-again), which reduces to (micro-amb () (lambda (val next-try) (micro-print val) (main-loop next-try)) (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))) This time, the list of choices is empty, so micro-amb calls its fail procedure, which is the procedure (lambda () (display "no more values for ") (micro-print s) (micro-r-e-p))) This displays the message "no more values for (AMB 1 2)" and calls (micro-r-e-p) to start the read-eval-print loop all over again for a new problem.