diff options
author | bnewbold <bnewbold@eta.mit.edu> | 2009-03-27 19:57:08 -0400 |
---|---|---|
committer | bnewbold <bnewbold@eta.mit.edu> | 2009-03-27 19:57:08 -0400 |
commit | bfd30bfc1096486f0781e9caea3b15161488f3f6 (patch) | |
tree | ddfec6b61bbf2d1ec4631469ae78e9e541e4a3cf /ps07_amb | |
parent | 648b3b85668e68e6b4676cbe01e530baec653d4a (diff) | |
download | 6.945-bfd30bfc1096486f0781e9caea3b15161488f3f6.tar.gz 6.945-bfd30bfc1096486f0781e9caea3b15161488f3f6.zip |
ps07 assignment
Diffstat (limited to 'ps07_amb')
-rw-r--r-- | ps07_amb/ambsch.scm | 336 | ||||
-rw-r--r-- | ps07_amb/examples.scm | 189 | ||||
-rw-r--r-- | ps07_amb/funco.scm | 223 | ||||
-rw-r--r-- | ps07_amb/load.scm | 11 | ||||
-rw-r--r-- | ps07_amb/ps.txt | 1355 | ||||
-rw-r--r-- | ps07_amb/stack-queue.scm | 52 |
6 files changed, 2166 insertions, 0 deletions
diff --git a/ps07_amb/ambsch.scm b/ps07_amb/ambsch.scm new file mode 100644 index 0000000..7e4bfe8 --- /dev/null +++ b/ps07_amb/ambsch.scm @@ -0,0 +1,336 @@ +;;;; Extension of Scheme for amb +;;; amb is the ambiguous operator of McCarthy. + +;;; (load "stack-queue.scm") + +(define-syntax amb + (sc-macro-transformer + (lambda (form uenv) + `(amb-list + (list ,@(map (lambda (arg) + `(lambda () + ,(close-syntax arg uenv))) + (cdr form))))))) + +(define *number-of-calls-to-fail* 0) ;for metering. + +(define (amb-list alternatives) + (if (null? alternatives) + (set! *number-of-calls-to-fail* + (+ *number-of-calls-to-fail* 1))) + (call-with-current-continuation + (lambda (k) + (add-to-search-schedule + (map (lambda (alternative) + (lambda () + (within-continuation k alternative))) + alternatives)) + (yield)))) + + +;;; amb-set! is an assignment operator +;;; that gets undone on backtracking. + +(define-syntax amb-set! + (sc-macro-transformer + (lambda (form uenv) + (compile-amb-set (cadr form) (caddr form) uenv)))) + +(define (compile-amb-set var val-expr uenv) + (let ((var (close-syntax var uenv)) + (val (close-syntax val-expr uenv))) + `(let ((old-value ,var)) + (effect-wrapper + (lambda () + (set! ,var ,val)) + (lambda () + (set! ,var old-value)))))) + + +;;; A general wrapper for undoable effects + +(define (effect-wrapper doer undoer) + (force-next + (lambda () (undoer) (yield))) + (doer)) + +;;; Alternative search strategy wrappers + +(define (with-depth-first-schedule thunk) + (call-with-current-continuation + (lambda (k) + (fluid-let ((add-to-search-schedule + add-to-depth-first-search-schedule) + (*search-schedule* (empty-search-schedule)) + (*top-level* k)) + (thunk))))) + +(define (with-breadth-first-schedule thunk) + (call-with-current-continuation + (lambda (k) + (fluid-let ((add-to-search-schedule + add-to-breadth-first-search-schedule) + (*search-schedule* (empty-search-schedule)) + (*top-level* k)) + (thunk))))) + + +;;; Representation of the search schedule + +(define *search-schedule*) + +(define (empty-search-schedule) + (make-stack&queue)) + +(define (yield) + (if (stack&queue-empty? *search-schedule*) + (*top-level* #f) + ((pop! *search-schedule*)))) + +(define (force-next thunk) + (push! *search-schedule* thunk)) + +;;; Alternative search strategies + +(define (add-to-depth-first-search-schedule alternatives) + (for-each (lambda (alternative) + (push! *search-schedule* alternative)) + (reverse alternatives))) + +(define (add-to-breadth-first-search-schedule alternatives) + (for-each (lambda (alternative) + (add-to-end! *search-schedule* alternative)) + alternatives)) + +;;; For incremental interactive experiments from REPL. + +(define (init-amb) + (set! *search-schedule* (empty-search-schedule)) + (set! *number-of-calls-to-fail* 0) + 'done) + +(define add-to-search-schedule ;; Default is depth 1st + add-to-depth-first-search-schedule) + +(define *top-level* + (lambda (ignore) + (display ";No more alternatives\n") + (abort->top-level unspecific))) + +;;; AX 1 - Elementary backtrack test. + +(define elementary-backtrack-test + (lambda () + (let ((x (amb 1 2 3))) + (pp (list x)) + (let ((y (amb 'a 'b))) + (pp (list x y)) + (let ((z (amb #t #f))) + (pp (list x y z))))) + (amb))) +#| +;; AX 1.d - Elementary backtrack test. [Depth First] + +(with-depth-first-schedule elementary-backtrack-test) +(1) +(1 a) +(1 a #t) +(1 a #f) +(1 b) +(1 b #t) +(1 b #f) +(2) +(2 a) +(2 a #t) +(2 a #f) +(2 b) +(2 b #t) +(2 b #f) +(3) +(3 a) +(3 a #t) +(3 a #f) +(3 b) +(3 b #t) +(3 b #f) +;Value: #f + +;; AX 1.b - Elementary backtrack test. [Breadth First] + +(with-breadth-first-schedule elementary-backtrack-test) +(1) +(2) +(3) +(1 a) +(1 b) +(2 a) +(2 b) +(3 a) +(3 b) +(1 a #t) +(1 a #f) +(1 b #t) +(1 b #f) +(2 a #t) +(2 a #f) +(2 b #t) +(2 b #f) +(3 a #t) +(3 a #f) +(3 b #t) +(3 b #f) +;Value: #f +|# + +;;; AX 2 - Testing undoable assignment. + +(define testing-undoable-assignment + (lambda () + (let ((x (amb 1 2 3)) (y 0) (z 0)) + (pp `(before ,x ,y ,z)) + (amb-set! y x) + (pp `(after ,x ,y ,z)) + (amb-set! z (amb 3.14 2.718)) + (pp `(zset ,x ,y ,z)) + (amb-set! x (+ y z)) + (pp `(xset ,x ,y ,z)) + (amb)))) +#| +;;; AX 2.d - Testing undoable assignment. [Depth First] + +(with-depth-first-schedule testing-undoable-assignment) +(before 1 0 0) +(after 1 1 0) +(zset 1 1 3.14) +(xset 4.140000000000001 1 3.14) +(zset 1 1 2.718) +(xset 3.718 1 2.718) +(before 2 0 0) +(after 2 2 0) +(zset 2 2 3.14) +(xset 5.140000000000001 2 3.14) +(zset 2 2 2.718) +(xset 4.718 2 2.718) +(before 3 0 0) +(after 3 3 0) +(zset 3 3 3.14) +(xset 6.140000000000001 3 3.14) +(zset 3 3 2.718) +(xset 5.718 3 2.718) +;Value: #f +|# + +;;; AX 3 - Pythagorean triples + +;; In breadth-first we get useful results here. +;; None from depth-first. + +;; AX 3.f - A Pythagorean triple from... + +(define (a-pythagorean-triple-from low) + (let ((i (an-integer-from low))) + (let ((j (an-integer-from i))) + (let ((k (an-integer-from j))) + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + +(define (require p) + (if (not p) (amb))) + +(define (an-integer-from low) + (amb low (an-integer-from (+ low 1)))) + +#| +(with-breadth-first-schedule + (lambda () + (pp (a-pythagorean-triple-from 1)) + (amb))) +(3 4 5) +(6 8 10) +(5 12 13) +(9 12 15) +(8 15 17) +(12 16 20) +(7 24 25) +(15 20 25) +(10 24 26) +(20 21 29) +(18 24 30) +(16 30 34) +(21 28 35) +(12 35 37) +(15 36 39) +(24 32 40) +(9 40 41) +(27 36 45) +(14 48 50) +(30 40 50) +(24 45 51) +(20 48 52) +(28 45 53) +(33 44 55) +(40 42 58) +(36 48 60) +(11 60 61) +(16 63 65) +(25 60 65) +(33 56 65) +;Quit! +|# + +;; AX 3.b - A Pythagorean triple between... + +;; For example, for controlling search: + +(define (a-pythagorean-triple-between low high) + (let ((i (an-integer-between low high))) + (let ((j (an-integer-between i high))) + (let ((k (an-integer-between j high))) + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + +(define (an-integer-between low high) + (require (<= low high)) + (amb low + (an-integer-between (+ low 1) high))) + +;; A useful device: + +(define (amb-collect-values result-thunk #!optional limit) + (call-with-current-continuation + (lambda (k) + (let ((values '()) (count 0)) + (fluid-let ((*top-level* (lambda (ignore) (k values))) + (*search-schedule* (empty-search-schedule))) + (let ((value (result-thunk))) + (set! values (cons value values)) + (set! count (+ count 1)) + (if (and (not (default-object? limit)) + (>= count limit)) + (k values)) + (amb))))))) +#| +(with-depth-first-schedule + (lambda () + (let ((mid (amb-collect-values + (lambda () + (a-pythagorean-triple-between 1 20)) + ;; I want only 3, and + ;; I don't want to backtrack into this. + 3))) + (pp (list (a-pythagorean-triple-between 1 10) + mid + (a-pythagorean-triple-between 10 30))) + (amb)))) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (10 24 26)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (10 24 26)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (12 16 20)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (12 16 20)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (15 20 25)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (15 20 25)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (18 24 30)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (18 24 30)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (20 21 29)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (20 21 29)) +;Value: #f +|# diff --git a/ps07_amb/examples.scm b/ps07_amb/examples.scm new file mode 100644 index 0000000..e1162d6 --- /dev/null +++ b/ps07_amb/examples.scm @@ -0,0 +1,189 @@ +#| +|| SICP Section 4.3.2 +|| Logic Puzzles +|| +|| Baker, Cooper, Fletcher, Miller, and Smith live on +|| different floors of a building that has only five +|| floors. Baker does not live on the top floor. +|| Cooper does not live on the bottom floor. Fletcher +|| does not live on either the top or the bottom +|| floor. Miller lives on a higher floor than does +|| Cooper. Smith does not live on a floor adjacent to +|| Fletcher's. Fletcher does not live on a floor +|| adjacent to Cooper's. Where does everyone live? +|| +|| (From Dinesman, 1968) +|# + +(define (multiple-dwelling) + (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))) + (require + (distinct? + (list baker cooper fletcher miller smith))) + (require (not (= baker 5))) + (require (not (= cooper 1))) + (require (not (= fletcher 5))) + (require (not (= fletcher 1))) + (require (> miller cooper)) + (require + (not (= (abs (- smith fletcher)) 1))) + (require + (not (= (abs (- fletcher cooper)) 1))) + (list (list 'baker baker) + (list 'cooper cooper) + (list 'fletcher fletcher) + (list 'miller miller) + (list 'smith smith)))) + +(define (distinct? items) + (cond ((null? items) #t) + ((null? (cdr items)) #t) + ((member (car items) (cdr items)) #f) + (else (distinct? (cdr items))))) + +#| +(init-amb) +;Value: done + +(with-depth-first-schedule multiple-dwelling) +;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1)) + +(amb) +;No more alternatives +|# + +;;; From SICP Section 4.3.2 +;;; Parsing natural language + +(define (parse input) + (amb-set! *unparsed* input) + (let ((sent (parse-sentence))) + (require (null? *unparsed*)) + sent)) + +(define *unparsed* '()) + +(define (parse-sentence) + (let* ((np (parse-noun-phrase)) + (verb (parse-verb-phrase))) + (list 'sentence np verb))) + +(define (parse-noun-phrase) + (define (maybe-extend noun-phrase) + (amb noun-phrase + (maybe-extend + (list 'noun-phrase + noun-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-s-noun-phrase))) + +(define (parse-verb-phrase) + (define (maybe-extend verb-phrase) + (amb verb-phrase + (maybe-extend + (list 'verb-phrase + verb-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-word verbs))) + +(define (parse-s-noun-phrase) + (let* ((article (parse-word articles)) + (noun (parse-word nouns))) + (list 's-noun-phrase article noun))) + +(define (parse-prepositional-phrase) + (let* ((preposition + (parse-word prepositions)) + (np (parse-noun-phrase))) + (list 'prep-phrase preposition np))) + +(define (parse-word word-list) + (require (not (null? *unparsed*))) + (require (memq (car *unparsed*) + (cdr word-list))) + (let ((found-word (car *unparsed*))) + (amb-set! *unparsed* (cdr *unparsed*)) + (list (car word-list) found-word))) + +(define nouns + '(noun student professor cat class)) + +(define verbs + '(verb studies lectures eats sleeps)) + +(define articles + '(article the a)) + +(define prepositions + '(prep for to in by with)) + +#| +(init-amb) +;Value: done + +(pp + (parse + '(the student with the cat sleeps in the class))) + +(sentence + (noun-phrase + (s-noun-phrase (article the) (noun student)) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat)))) + (verb-phrase + (verb sleeps) + (prep-phrase (prep in) + (s-noun-phrase (article the) + (noun class))))) +;Unspecified return value + +(amb) +;No more alternatives +|# + +#| +(init-amb) +;Value: done + +(pp + (parse + '(the professor lectures + to the student with the cat))) + +(sentence + (s-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb-phrase + (verb lectures) + (prep-phrase (prep to) + (s-noun-phrase (article the) + (noun student)))) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat))))) +;Unspecified return value + +(amb) + +(sentence + (s-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb lectures) + (prep-phrase + (prep to) + (noun-phrase + (s-noun-phrase (article the) + (noun student)) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat))))))) +;Unspecified return value + +(amb) +;No more alternatives +|# diff --git a/ps07_amb/funco.scm b/ps07_amb/funco.scm new file mode 100644 index 0000000..73fb947 --- /dev/null +++ b/ps07_amb/funco.scm @@ -0,0 +1,223 @@ +;;;; Fun with Continuations + +#| Adapted from MIT/GNU Scheme Reference Manual [Section 12.4]: + +(call-with-current-continuation + (lambda (exit) + (for-each (lambda (x) + (if (negative? x) + (exit x))) + '(54 0 37 -3 245 -19)) ; ** + #t)) +;Value: -3 + +|# + +;;; Continuations as Non-Local Exits + +(define (funco:first-negative list-of-numbers) + (call-with-current-continuation + (lambda (k_exit) + (or (call-with-current-continuation + (lambda (k_shortcut) + (for-each (lambda (n) + (cond ((not (number? n)) + (pp `(not-a-number: ,n)) + (k_exit #f)) + ((negative? n) + (k_shortcut n)) + (else + ':keep-looking))) + list-of-numbers) + #f ;; Fall-through sentinel: no negatives found. + )) + ':no-negatives-found)))) + +#| +(funco:first-negative '(54 0 37 -3 245 -19)) +;Value: -3 + +(funco:first-negative '(54 0 37 3 245 19)) +;Value: :no-negatives-found + +(funco:first-negative '(54 0 37 no 245 boo)) +(not-a-number: no) +;Value: #f +|# + +;;; Continuations for Proceeding (Suspend/Resume Backtracking) + +(define (funco:first-negative-n-proceed list-of-numbers) ;;; ** + (call-with-current-continuation + (lambda (k_exit) + (or (call-with-current-continuation + (lambda (k_shortcut) + (for-each (lambda (n) + (pp ;;; ** + (call-with-current-continuation ;;; ** + (lambda (k_proceed) ;;; ** + (cond ((not (number? n)) + (pp `(not-a-number: ,n)) + (k_exit + (cons n k_proceed))) ;;; ** + ((negative? n) + (k_shortcut + (cons n k_proceed))) ;;; ** + (else + ':keep-looking))) + ))) ;;; ** + list-of-numbers) + #f ;; Fall-through sentinel: no negatives found. + )) + ':no-negatives-found)))) + +(define (funco:first-negative-n-proceed-more? smore) (pair? smore)) +(define (funco:first-negative-n-proceed-more/found smore) (car smore)) +(define (funco:first-negative-n-proceed-more/k smore) (cdr smore)) +(define (funco:first-negative-n-proceed-more/next smore) + ((funco:first-negative-n-proceed-more/k smore) + (funco:first-negative-n-proceed-more/found smore))) + +#| +;;; ------------ +(define funco:first-of-two + (funco:first-negative-n-proceed '(54 0 37 -3 245 -19))) +:keep-looking +:keep-looking +:keep-looking +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:first-of-two) +;Value: -3 + +(funco:first-negative-n-proceed-more/next funco:first-of-two) +-3 +:keep-looking +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:first-of-two) +;Value: -19 + +(funco:first-negative-n-proceed-more/next funco:first-of-two) +-19 +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #f + +funco:first-of-two +;Value: :no-negatives-found +|# + +#| +;;; ---- +(define funco:nada + (funco:first-negative-n-proceed '(54 0 37 3 245 19))) +:keep-looking +:keep-looking +:keep-looking +:keep-looking +:keep-looking +:keep-looking +;Value: funco:nada + +(funco:first-negative-n-proceed-more? funco:nada) +;Value: #f + +funco:nada +;Value: :no-negatives-found +|# + +#| +;;; ---- +(define funco:nans + (funco:first-negative-n-proceed '(54 0 37 no 245 boo))) +:keep-looking +:keep-looking +:keep-looking +(not-a-number: no) +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:nans) +;Value: no + +(funco:first-negative-n-proceed-more/next funco:nans) +no +:keep-looking +(not-a-number: boo) +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #t + +(funco:first-negative-n-proceed-more/next funco:nans) +boo +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #f + +funco:nans +;Value: :no-negatives-found +|# + +;;; Continuations for Backtracking (Re-entrant 1st-Class Continuations) + +(define *k_re-funco*) +(define funco) + +#| +(begin + (set! funco (+ 2 (call-with-current-continuation + (lambda (k_re-funco) + (set! *k_re-funco* k_re-funco) + 3)))) + ':ok) +;Value: :ok + +funco +;Value: 5 + +(*k_re-funco* 4) +;Value: :ok + +funco +;Value: 6 + +(*k_re-funco* 5) +;Value: :ok + +funco +;Value: 7 +|# + +;;; Dynamic Contexts and Within-Continuation + +(define (funco:test-k-thunk k-thunk) + (let ((*foo* 2)) ;----------------------. + (define (foo-thunk) *foo*) ; *foo* is 2 out here. : + (call-with-current-continuation ; : + (lambda (k) ; : + (fluid-let ((*foo* 3)) ;---------------------. : + (k-thunk k foo-thunk) ; *foo* is 3 in here. : : + ) ;---------------------' : + )) ; *foo* is 2 out here. : + )) ;----------------------' +#| +(funco:test-k-thunk (lambda (k thunk) + (k (thunk)))) +;Value: 3 + +(funco:test-k-thunk (lambda (k thunk) + (within-continuation k thunk))) +;Value: 2 +|# diff --git a/ps07_amb/load.scm b/ps07_amb/load.scm new file mode 100644 index 0000000..5a0c783 --- /dev/null +++ b/ps07_amb/load.scm @@ -0,0 +1,11 @@ +;; Fun with Continuations +(load "funco") + +;; AMB Scheme extensions +(load "stack-queue") +(load "ambsch") + +;; AMB Examples from SICP +(load "examples") + +':have-fun! diff --git a/ps07_amb/ps.txt b/ps07_amb/ps.txt new file mode 100644 index 0000000..d70b8e1 --- /dev/null +++ b/ps07_amb/ps.txt @@ -0,0 +1,1355 @@ + + MASSACHVSETTS INSTITVTE OF TECHNOLOGY + Department of Electrical Engineering and Computer Science + + 6.945 Spring 2009 + Problem Set 7 + + Issued: Wed. 18 Mar. 2009 Due: Wed. 1 Apr. 2009 + + +Readings: + SICP second edition + Section 4.3: + Variations on a Scheme--Nondeterministic Programming + + Online MIT/GNU Scheme Documentation, + Section 2.3: Dynamic Binding - fluid-let + Section 12.4: Continuations - call-with-current-continuation & + within-continuation + + There is an entire bibliography of stuff about this on: + http://library.readscheme.org/page6.html + +Code: load.scm, funco.scm, ambsch.scm, stack-queue, examples.scm (attached) + + + Generate and Test + +We normally think of generate and test, and its extreme use in search, +as an AI technique. However, it can be viewed as a way of making +systems that are modular and independently evolvable, as in the +exploratory behavior of biological systems. Consider a very simple +example: suppose we have to solve a quadratic equation. There are two +roots to a quadratic. We could return both, and assume that the user +of the solution knows how to deal with that, or we could return one +and hope for the best. (The canonical sqrt routine returns the +positive square root, even though there are two square roots!) The +disadvantage of returning both solutions is that the receiver of that +result must know to try the computation with both and either reject +one, for good reason, or return both results of the computation, which +may itself have made some choices. The disadvantage of returning only +one solution is that it may not be the right one for the receiver's +purpose. + +A better way to handle this is to build a backtracking mechanism into +the infrastructure. The square-root procedure should return one of +the roots, with the option to change its mind and return the other one +if the first choice is determined to be inappropriate by the receiver. +It is, and should be, the receiver's responsibility to determine if +the ingredients to its computation are appropriate and acceptable. +This may itself require a complex computation, involving choices whose +consequences may not be apparent without further computation, so the +process is recursive. Of course, this gets us into potentially deadly +exponential searches through all possible assignments to all the +choices that have been made in the program. As usual, modular +flexibility can be dangerous. + + Linguistically Implicit Search + +We have talked about the extent to which a search strategy can be +separated from the other parts of a program, so that one can +interchange search strategies without greatly modifying the program. +In this problem set we take the further step of pushing search and +search control into the infrastructure that is supported by the +language, without explicitly building search into our program at all. + +This idea has considerable history. In 1961 John McCarthy had the +idea of a nondeterministic operator AMB, which could be useful for +representing nondeterministic automata. In 1967 Bob Floyd had the +idea of building backtracking search into a computer language as part +of the linguistic glue. In 1969 Carl Hewitt proposed a language, +PLANNER, that embodied these ideas. In the early 1970s Colmerauer, +Kowalski, Roussel, and Warren developed Prolog, a language based on a +limited form of first-order predicate calculus, which made +backtracking search implicit. + +In this problem set we will learn how to implement and how to use +linguistic nondeterminism. Before proceeding we recommend that you +carefully reread section 4.3, up to but not including 4.3.3 of SICP +(pages 412--426). This material introduces AMB and shows how it can +be used to formalize some classes of search problems. Section 4.3.3 +describes how to compile a language that includes AMB into a +combinator structure. We touched on this in Problem Set 4. In this +problem set we will see a different way to implement AMB, worked out +in the file "ambsch.scm", which allows ordinary Scheme programs to +freely intermix with code that includes nondeterministic search. + +But before we try to understand the implementation, it is useful to +review what can be done with AMB. If you load "ambsch.scm" into MIT +Scheme you can run examples such as the ones in the comments at the +end of the "ambsch.scm" file, and you can solve the following puzzle. + +------------- +Problem 7.1: Warmup (From SICP Exercise 4.43, p.420) + +Formalize and solve the following puzzle with AMB: + + Mary Ann Moore's father has a yacht and so has each of his + four friends: Colonel Downing, Mr. Hall, Sir Barnacle Hood, + and Dr. Parker. Each of the five also has one daughter and + each has named his yacht after a daughter of one of the + others. Sir Barnacle's yacht is the Gabrielle, Mr. Moore + owns the Lorna; Mr. Hall the Rosalind. The Melissa, owned + by Colonel Downing, is named after Sir Barnacle's daughter. + Gabrielle's father owns the yacht that is named after Dr. + Parker's daughter. Who is Lorna's father? + +You must use AMB to specify the alternatives that are possible for +each choice. Also determine how many solutions there are if we are +not told that Mary Ann's last name is Moore. +------------- + + Fun with Current Continuation + +Before we can understand how the ambsch mechanism works we have to get +deeper into continuations. Continuations are one of the most powerful +(and the most dangerous) tools of a programmer. Scheme provides the +ability for a programmer to get the continuation of an expression. +But most other languages do not support the use of first-class +continuations. (Some other languages that do have first-class +continuations include SML, Ruby, and Smalltalk.) + +Whenever a Scheme expression is evaluated, a continuation exists that +wants the result of the expression. The continuation represents an +entire (default) future for the computation. If the expression is +evaluated at top level, for example, the continuation will take the +result, print it on the screen, prompt for the next input, evaluate +it, and so on forever. Most of the time the continuation includes +actions specified by user code, as in a continuation that will take +the result, multiply it by the value stored in a local variable, add +seven, and give the answer to the top-level continuation to be +printed. Normally these ubiquitous continuations are hidden behind the +scenes and programmers don't think much about them. On the rare +occasions that you may need to deal explicitly with continuations, +call-with-current-continuation lets you do so by creating a procedure +that acts just like the current continuation. + +See the on-line MIT/GNU Scheme Reference Manual, Section 12.4, for a +detailed description of CALL-WITH-CURRENT-CONTINUATION. + +Explicit continuations may be powerful and sometimes useful, but they +are rarely necessary. One common usage case is for non-local exits. +Another is for resuming a suspended computation for backtracking. Yet +another is coroutining (which we will explore in a later problem set). + + + Continuations as Non-Local Exits + +Consider the following simple example of a non-local exit continuation +(adapted from the MIT/GNU Scheme Reference Manual [Section 12.4]): + + (call-with-current-continuation + (lambda (exit) + (for-each (lambda (x) + (if (negative? x) + (exit x))) + '(54 0 37 -3 245 -19)) ; ** + #t)) + ;Value: -3 + +Because Scheme's for-each procedure walks the list in left-to-right +order, the first negative element encountered is -3, which is +immediately returned. Had the list contained no negative numbers, the +result would have been #t (since the body of the lambda form is a +sequence of two expressions, the for-each expression followed by #t). + +In a larger context, this might appear within some other form, like +the following definition (explained below) in file "funco.scm": + + (define (funco:first-negative list-of-numbers) + (call-with-current-continuation + (lambda (k_exit) + (or (call-with-current-continuation + (lambda (k_shortcut) + (for-each (lambda (n) + (cond ((not (number? n)) + (pp `(not-a-number: ,n)) + (k_exit #f)) + ((negative? n) + (k_shortcut n)) + (else + ':keep-looking))) + list-of-numbers) + #f ;; Fall-through sentinel: no negatives found. + )) + ':no-negatives-found)))) + + #| + (funco:first-negative '(54 0 37 -3 245 -19)) + ;Value: -3 + + (funco:first-negative '(54 0 37 3 245 19)) + ;Value: :no-negatives-found + + (funco:first-negative '(54 0 37 no 245 boo)) + (not-a-number: no) + ;Value: #f + |# + +This demonstrates nested continuations, where the outermost k_exit +continuation exits the entire call to funco:first-negative while the +inner k_shortcut continuation exits only to the enclosing disjunction +(or), then continues from there. + +In short, if a continuation captured by call-with-current-continuation +is ever invoked (with value V), then the computation will continue by +returning V as the value of the call to call-with-current-continuation +and resuming execution normally from there. [This is a bit tricky so +look at the code above and re-read this last sentence a couple times +until it makes sense... and please suggest alternative wording that +might be less quixotically obtuse.] + +------------- +Problem 7.2: + +A. Define a simple procedure, snark-hunt, that takes a tree of symbols + as argument and recursively descends it looking for the symbol + 'snark at any leaf. It should immediately halt and return #t if + one is found; #f otherwise. Use call-with-current-continuation. + + If it helps, feel free to assume that all input trees will be valid + non-null lists of tree-or-symbol elements, or whatever other data + representation you find convenient. + + E.g., + + (snark-hunt '(((a b c) d (e f)) g (((snark . "oops") h) (i . j)))) + ;Value: #t + + Note that the dotted pairs in the above violate (intentionally) the + assumption that the input is comprised solely of proper lists of + tree-or-symbol elements, so overruns may well result in errors. + +B. How might you verify that it exits immediately rather than silently + returning through multiple return levels? Define a new procedure, + snark-hunt/instrumented, to demonstrate this. [Hint: setting an + exit status flag then signaling an error on wayward return paths + might work if placed carefully, but simply tracing via pp may be + easier. Whatever quick and dirty hack that works will do. The + goal here is to build your intuition about continuations, not to + ship product-quality code. Briefly explain your strategy.] +------------- + + Continuations for Backtracking + +The preceding was somewhat simplistic since the continuations captured +were used only for non-local exits. Specifically, they were not used +for backtracking. Moreover, they were never re-entered once invoked. + +Now consider the following slightly more interesting scenario: + + (define *k_re-funco*) + (define funco) + + #| + (begin + (set! funco (+ 2 (call-with-current-continuation + (lambda (k_re-funco) + (set! *k_re-funco* k_re-funco) + 3)))) + ':ok) + ;Value: :ok + + funco + ;Value: 5 + + (*k_re-funco* 4) + ;Value: :ok + + funco + ;Value: 6 + + (*k_re-funco* 5) + ;Value: :ok + + funco + ;Value: 7 + |# + +Note carefully how re-entering this captured continuation returns +control to the point before the add and, therefore, before assigning +variable funco and returning the symbol ':ok. This is why invoking it +always returns the symbol ':ok, not the value passed to the exported +continuation being re-entered (obviously) and not the new value to +which that variable is re-assigned nor its old value nor unspecific. + +This and the other examples in file "funco.scm" (attached) demonstrate +how to re-enter a captured continuation to proceed from intermediate +return points. This mechanism is used for backtracking in "ambsch.scm". + + Continuations and Dynamic Contexts + +We've already seen a few instances of dynamic binding via FLUID-LET in +lecture. Although assignment violates referential transparency, fluid +binding can be handy for locally overriding a free variable's value. + +For example, consider the following code fragment: + + (define *trace?* #f) + + (define (foo x) + (set! *trace?* #t) + (let ((result (bar x))) ;; bar may pp status when *trace?* set + (set! *trace?* #f) + result)) + +This works as expected only so long as bar does not capture and export +a continuation that can be used to re-enter bar's body. Moreover, if +bar exits by invoking a continuation that bypasses the normal return +mechanism that LET-binds result, the *trace?* flag may not be reset on +the way out. Worse, this presumes *trace?* is always #f on entry. + +To handle side-effects like this in the face of (possibly hidden) +first-class continuations, a new dynamic binding form named FLUID-LET +is provided that assigns (rather than LET-binds) variables on entry +and reassigns them to their previous values upon exit, whether exiting +via the normal return mechanism or through some captured continuation. + +Thus, FLUID-LET allows parameterization of subsystems with a condition +that is in effect over a controlled time interval (an extent) rather +than over a lexically apparent textual region of code (a scope). + +The FLUID-LET special form is documented in the on-line MIT/GNU Scheme +Reference Manual, Section 2.3 Dynamic Binding (q.v.). + +In this case, for example, the expected behavior can be achieved by +rewriting the above code fragment as: + + (define (foo x) + (fluid-let ((*trace?* #t)) + (bar x))) + +This mechanism is used in a few places in "ambsch.scm" to allow +arbitrary nesting of depth-first verse breadth-first scheduling. +It is also used by the mildly hackish amb-collect-values device. + + Dynamic Contexts and Within-Continuation + +The story gets really interesting when we define a thunk (a procedure +of no arguments) at some control point in order to delay evaluation of +its body, but we wish to invoke it in the dynamic context of its +definition's control point, not the dynamic context in flight at its +eventual point of call. + +For example, consider the following slightly contrived code fragment: + + (define (funco:test-k-thunk k-thunk) + (let ((*foo* 2)) ;----------------------. + (define (foo-thunk) *foo*) ; *foo* is 2 out here. : + (call-with-current-continuation ; : + (lambda (k) ; : + (fluid-let ((*foo* 3)) ;---------------------. : + (k-thunk k foo-thunk) ; *foo* is 3 in here. : : + ) ;---------------------' : + )) ; *foo* is 2 out here. : + )) ;----------------------' + + #| + (funco:test-k-thunk (lambda (k thunk) + (k (thunk)))) + ;Value: 3 + + (funco:test-k-thunk (lambda (k thunk) + (within-continuation k thunk))) + ;Value: 2 + |# + +The WITHIN-CONTINUATION procedure is documented in the MIT/GNU Scheme +Reference Manual [Section 12.4]). In short, it unrolls the dynamic +context to that of the continuation, k, before invoking the thunk, the +result of which is then passed to the continuation, k. + +In "ambsch.scm", WITHIN-CONTINUATION is used to ensure that sibling +AMB arguments are called in the dynamic context in which they were +introduced, not the dynamic context in which they are eventually +invoked. This not only ensures that each AMB alternative backtracks +to appropriate nested search strategies, it also avoids unnecessary +accumulation of control state during the invocation of alternatives. + + From Continuations to AMB + +Now that we have had experience with explicit expression continuations +we can begin to understand the code in "ambsch.scm". The heart of the +backtracker is amb-list, which takes a sequence of sibling thunks, +each representing an alternative value for the amb expression. The +thunks were produced by the amb macro, which syntactically transforms +amb expressions into amb-list expressions, as follows: + + (amb <e1> ... <en>) ==> + (amb-list (list (lambda () <e1>) ... (lambda () <en>))) + +The search schedule maintains an agenda of thunks that proceed the +computation when it is necessary for an amb expression to return with +a new alternative value. For a particular amb expression these thunks +are constructed so as to return from that amb expression, using the +continuation, k, captured at the entrance to its enclosing amb-list. +The within-continuation expression, which is almost equivalent to the +call (k (alternative)), prevents the capture of pieces of the control +stack that are unnecessary for continuing the computation correctly. + +Ambl first adds the returners for its alternative values to the search +schedule and then yields control to the first pending returner. + + (define (amb-list alternatives) + (if (null? alternatives) + (set! *number-of-calls-to-fail* + (+ *number-of-calls-to-fail* 1))) + (call-with-current-continuation + (lambda (k) + (add-to-search-schedule + (map (lambda (alternative) + (lambda () + (within-continuation k alternative))) + alternatives)) + (yield)))) + + + (define (yield) + (if (stack&queue-empty? *search-schedule*) + (*top-level* #f) + ((pop! *search-schedule*)))) + + +Note that procedure add-to-search-schedule is fluid bound either to +add-to-depth-first-search-schedule (the default behavior) or else to +add-to-breadth-first-search-schedule. See "ambsch.scm" for details. + + Breadth -v- Depth + +Consider the following experiment: + + (define (a-pythagorean-triple-between low high) + (let ((i (an-integer-between low high))) + (let ((j (an-integer-between i high))) + (let ((k (an-integer-between j high))) + (set! count (+ count 1)) ; ** + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + + (define count 0) + #| + (begin + (init-amb) + (set! count 0) + (with-breadth-first-schedule + (lambda () (pp (a-pythagorean-triple-between 10 20))))) + (12 16 20) + + count + ;Value: 246 + + *number-of-calls-to-fail* + ;Value: 282 + + + (begin + (init-amb) + (set! count 0) + (with-depth-first-schedule + (lambda () (pp (a-pythagorean-triple-between 10 20))))) + (12 16 20) + + count + ;Value: 156 + + *number-of-calls-to-fail* + ;Value: 182 + |# + +------------- +Problem 7.3: + +Explain the different counts between depth-first and breadth-first +(in rough terms, not the exact counts). + +Also, where are the extra calls to fail coming from? + +Considering that the breadth-first search does more work, why is the +a-pythagorean-triple-from search [AX 3.f in "ambsch.scm"] not usable +under the depth-first search strategy? +------------- + + Less Deterministic Non-Determinism + +Eva Lu Ator chides that a criticism one might make of our AMB +implementation is that it's not as non-deterministic as one might +sometimes like. Specifically, given a list of alternatives in an AMB +form, we always choose the leftmost alternative first then the second +leftmost and so on in left-to-right order. + +She suggests that one might wish to override this choice, say, with +right-to-left alternation or even in random order. Specifically, +she'd like something like: + + (with-left-to-right-alternation <thunk>) + (with-right-to-left-alternation <thunk>) + (with-random-order-alternation <thunk>) + +She's quick to point out that this choice is independent of the choice +of depth-first or breadth-first (or whatever else) search order one +might choose. + +------------- +Problem 7.5: + +A. Under what circumstances might you want an unordered (random) AMB? + Craft a specific short example to use as a test case below. + +B. Implement these three alternatives and give an example use of each. + For simplicity and uniformity, model your code after that for + with-depth-first-schedule, add-to-depth-first-search-schedule, etc. + [Hint: Feel free to use the native MIT Scheme RANDOM procedure.] +------------- + + Neurological Origami + +Consider the following brain twister: + + (define moby-brain-twister-test + (lambda () + (let ((x) (y) (z)) + (set! x (amb 1 2 3)) + (pp (list x)) + (set! y (amb 'a 'b)) + (pp (list x y)) + (set! z (amb #t #f)) + (pp (list x y z)) + (amb)))) + #| + (with-breadth-first-schedule moby-brain-twister-test) + (1) + (2) + (3) + (3 a) + (3 b) + (3 a) + (3 b) + (3 a) + (3 b) + (3 b #t) + (3 b #f) + (3 b #t) + (3 b #f) + (3 b #t) + (3 b #f) + (3 b #t) + (3 b #f) + (3 b #t) + (3 b #f) + (3 b #t) + (3 b #f) + ;Value: #f + |# + +Contrast this trace with the breadth-first elementary backtrack test +AMB example from "ambsch.scm" [viz., AX 1.b]. + +------------- +Problem 7.6: + +Why does this weird thing happen? + +The explanation is very simple, but this took us many hours to +understand. + +[Hint: Look at (with-depth-first-schedule moby-brain-twister-test).] +------------- + + A Potential Project Topic + +------------- +Problem 7.7: (optional!) + +In the ``Continuations and Dynamic Contexts'' discussion section +above, it was claimed that the breadth-first and depth-first search +strategies can be arbitrarily nested within AMB forms. + +Does the nesting of depth-first and breadth-first scheduling work +correctly as currently implemented in "ambsch.scm"? Specifically, +design an experiment that exposes the bug (if there is one) or that +demonstrates anecdotally that it does work correctly (if it does). +Explain your rationale. + +This involves crafting a couple experiments that distinguish between +depth-first and breadth-first search strategies then composing them in +interesting ways to demonstrate local control over nested searches. + +Identifying a natural class of problems for which this flexibility is +useful--- not just hacked together to prove a point--- might be a fine +topic for an independent project. Don't spend too much time on it yet. +------------- + +;;;; File: funco.scm + +;;;; Fun with Continuations + +#| Adapted from MIT/GNU Scheme Reference Manual [Section 12.4]: + +(call-with-current-continuation + (lambda (exit) + (for-each (lambda (x) + (if (negative? x) + (exit x))) + '(54 0 37 -3 245 -19)) ; ** + #t)) +;Value: -3 + +|# + +;;; Continuations as Non-Local Exits + +(define (funco:first-negative list-of-numbers) + (call-with-current-continuation + (lambda (k_exit) + (or (call-with-current-continuation + (lambda (k_shortcut) + (for-each (lambda (n) + (cond ((not (number? n)) + (pp `(not-a-number: ,n)) + (k_exit #f)) + ((negative? n) + (k_shortcut n)) + (else + ':keep-looking))) + list-of-numbers) + #f ;; Fall-through sentinel: no negatives found. + )) + ':no-negatives-found)))) + +#| +(funco:first-negative '(54 0 37 -3 245 -19)) +;Value: -3 + +(funco:first-negative '(54 0 37 3 245 19)) +;Value: :no-negatives-found + +(funco:first-negative '(54 0 37 no 245 boo)) +(not-a-number: no) +;Value: #f +|# + +;;; Continuations for Proceeding (Suspend/Resume Backtracking) + +(define (funco:first-negative-n-proceed list-of-numbers) ;;; ** + (call-with-current-continuation + (lambda (k_exit) + (or (call-with-current-continuation + (lambda (k_shortcut) + (for-each (lambda (n) + (pp ;;; ** + (call-with-current-continuation ;;; ** + (lambda (k_proceed) ;;; ** + (cond ((not (number? n)) + (pp `(not-a-number: ,n)) + (k_exit + (cons n k_proceed))) ;;; ** + ((negative? n) + (k_shortcut + (cons n k_proceed))) ;;; ** + (else + ':keep-looking))) + ))) ;;; ** + list-of-numbers) + #f ;; Fall-through sentinel: no negatives found. + )) + ':no-negatives-found)))) + +(define (funco:first-negative-n-proceed-more? smore) (pair? smore)) +(define (funco:first-negative-n-proceed-more/found smore) (car smore)) +(define (funco:first-negative-n-proceed-more/k smore) (cdr smore)) +(define (funco:first-negative-n-proceed-more/next smore) + ((funco:first-negative-n-proceed-more/k smore) + (funco:first-negative-n-proceed-more/found smore))) + +#| +;;; ------------ +(define funco:first-of-two + (funco:first-negative-n-proceed '(54 0 37 -3 245 -19))) +:keep-looking +:keep-looking +:keep-looking +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:first-of-two) +;Value: -3 + +(funco:first-negative-n-proceed-more/next funco:first-of-two) +-3 +:keep-looking +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:first-of-two) +;Value: -19 + +(funco:first-negative-n-proceed-more/next funco:first-of-two) +-19 +;Value: funco:first-of-two + +(funco:first-negative-n-proceed-more? funco:first-of-two) +;Value: #f + +funco:first-of-two +;Value: :no-negatives-found +|# + +#| +;;; ---- +(define funco:nada + (funco:first-negative-n-proceed '(54 0 37 3 245 19))) +:keep-looking +:keep-looking +:keep-looking +:keep-looking +:keep-looking +:keep-looking +;Value: funco:nada + +(funco:first-negative-n-proceed-more? funco:nada) +;Value: #f + +funco:nada +;Value: :no-negatives-found +|# + +#| +;;; ---- +(define funco:nans + (funco:first-negative-n-proceed '(54 0 37 no 245 boo))) +:keep-looking +:keep-looking +:keep-looking +(not-a-number: no) +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #t + +(funco:first-negative-n-proceed-more/found funco:nans) +;Value: no + +(funco:first-negative-n-proceed-more/next funco:nans) +no +:keep-looking +(not-a-number: boo) +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #t + +(funco:first-negative-n-proceed-more/next funco:nans) +boo +;Value: funco:nans + +(funco:first-negative-n-proceed-more? funco:nans) +;Value: #f + +funco:nans +;Value: :no-negatives-found +|# + +;;; Continuations for Backtracking (Re-entrant 1st-Class Continuations) + +(define *k_re-funco*) +(define funco) + +#| +(begin + (set! funco (+ 2 (call-with-current-continuation + (lambda (k_re-funco) + (set! *k_re-funco* k_re-funco) + 3)))) + ':ok) +;Value: :ok + +funco +;Value: 5 + +(*k_re-funco* 4) +;Value: :ok + +funco +;Value: 6 + +(*k_re-funco* 5) +;Value: :ok + +funco +;Value: 7 +|# + +;;; Dynamic Contexts and Within-Continuation + +(define (funco:test-k-thunk k-thunk) + (let ((*foo* 2)) ;----------------------. + (define (foo-thunk) *foo*) ; *foo* is 2 out here. : + (call-with-current-continuation ; : + (lambda (k) ; : + (fluid-let ((*foo* 3)) ;---------------------. : + (k-thunk k foo-thunk) ; *foo* is 3 in here. : : + ) ;---------------------' : + )) ; *foo* is 2 out here. : + )) ;----------------------' +#| +(funco:test-k-thunk (lambda (k thunk) + (k (thunk)))) +;Value: 3 + +(funco:test-k-thunk (lambda (k thunk) + (within-continuation k thunk))) +;Value: 2 +|# + +;;;; File: ambsch.scm +;;;; Extension of Scheme for amb +;;; amb is the ambiguous operator of McCarthy. + +;;; (load "stack-queue.scm") + +(define-syntax amb + (sc-macro-transformer + (lambda (form uenv) + `(amb-list + (list ,@(map (lambda (arg) + `(lambda () + ,(close-syntax arg uenv))) + (cdr form))))))) + +(define *number-of-calls-to-fail* 0) ;for metering. + +(define (amb-list alternatives) + (if (null? alternatives) + (set! *number-of-calls-to-fail* + (+ *number-of-calls-to-fail* 1))) + (call-with-current-continuation + (lambda (k) + (add-to-search-schedule + (map (lambda (alternative) + (lambda () + (within-continuation k alternative))) + alternatives)) + (yield)))) + + +;;; amb-set! is an assignment operator +;;; that gets undone on backtracking. + +(define-syntax amb-set! + (sc-macro-transformer + (lambda (form uenv) + (compile-amb-set (cadr form) (caddr form) uenv)))) + +(define (compile-amb-set var val-expr uenv) + (let ((var (close-syntax var uenv)) + (val (close-syntax val-expr uenv))) + `(let ((old-value ,var)) + (effect-wrapper + (lambda () + (set! ,var ,val)) + (lambda () + (set! ,var old-value)))))) + + +;;; A general wrapper for undoable effects + +(define (effect-wrapper doer undoer) + (force-next + (lambda () (undoer) (yield))) + (doer)) + +;;; Alternative search strategy wrappers + +(define (with-depth-first-schedule thunk) + (call-with-current-continuation + (lambda (k) + (fluid-let ((add-to-search-schedule + add-to-depth-first-search-schedule) + (*search-schedule* (empty-search-schedule)) + (*top-level* k)) + (thunk))))) + +(define (with-breadth-first-schedule thunk) + (call-with-current-continuation + (lambda (k) + (fluid-let ((add-to-search-schedule + add-to-breadth-first-search-schedule) + (*search-schedule* (empty-search-schedule)) + (*top-level* k)) + (thunk))))) + + +;;; Representation of the search schedule + +(define *search-schedule*) + +(define (empty-search-schedule) + (make-stack&queue)) + +(define (yield) + (if (stack&queue-empty? *search-schedule*) + (*top-level* #f) + ((pop! *search-schedule*)))) + +(define (force-next thunk) + (push! *search-schedule* thunk)) + +;;; Alternative search strategies + +(define (add-to-depth-first-search-schedule alternatives) + (for-each (lambda (alternative) + (push! *search-schedule* alternative)) + (reverse alternatives))) + +(define (add-to-breadth-first-search-schedule alternatives) + (for-each (lambda (alternative) + (add-to-end! *search-schedule* alternative)) + alternatives)) + +;;; For incremental interactive experiments from REPL. + +(define (init-amb) + (set! *search-schedule* (empty-search-schedule)) + (set! *number-of-calls-to-fail* 0) + 'done) + +(define add-to-search-schedule ;; Default is depth 1st + add-to-depth-first-search-schedule) + +(define *top-level* + (lambda (ignore) + (display ";No more alternatives\n") + (abort->top-level unspecific))) + +;;; AX 1 - Elementary backtrack test. + +(define elementary-backtrack-test + (lambda () + (let ((x (amb 1 2 3))) + (pp (list x)) + (let ((y (amb 'a 'b))) + (pp (list x y)) + (let ((z (amb #t #f))) + (pp (list x y z))))) + (amb))) +#| +;; AX 1.d - Elementary backtrack test. [Depth First] + +(with-depth-first-schedule elementary-backtrack-test) +(1) +(1 a) +(1 a #t) +(1 a #f) +(1 b) +(1 b #t) +(1 b #f) +(2) +(2 a) +(2 a #t) +(2 a #f) +(2 b) +(2 b #t) +(2 b #f) +(3) +(3 a) +(3 a #t) +(3 a #f) +(3 b) +(3 b #t) +(3 b #f) +;Value: #f + +;; AX 1.b - Elementary backtrack test. [Breadth First] + +(with-breadth-first-schedule elementary-backtrack-test) +(1) +(2) +(3) +(1 a) +(1 b) +(2 a) +(2 b) +(3 a) +(3 b) +(1 a #t) +(1 a #f) +(1 b #t) +(1 b #f) +(2 a #t) +(2 a #f) +(2 b #t) +(2 b #f) +(3 a #t) +(3 a #f) +(3 b #t) +(3 b #f) +;Value: #f +|# + +;;; AX 2 - Testing undoable assignment. + +(define testing-undoable-assignment + (lambda () + (let ((x (amb 1 2 3)) (y 0) (z 0)) + (pp `(before ,x ,y ,z)) + (amb-set! y x) + (pp `(after ,x ,y ,z)) + (amb-set! z (amb 3.14 2.718)) + (pp `(zset ,x ,y ,z)) + (amb-set! x (+ y z)) + (pp `(xset ,x ,y ,z)) + (amb)))) +#| +;;; AX 2.d - Testing undoable assignment. [Depth First] + +(with-depth-first-schedule testing-undoable-assignment) +(before 1 0 0) +(after 1 1 0) +(zset 1 1 3.14) +(xset 4.140000000000001 1 3.14) +(zset 1 1 2.718) +(xset 3.718 1 2.718) +(before 2 0 0) +(after 2 2 0) +(zset 2 2 3.14) +(xset 5.140000000000001 2 3.14) +(zset 2 2 2.718) +(xset 4.718 2 2.718) +(before 3 0 0) +(after 3 3 0) +(zset 3 3 3.14) +(xset 6.140000000000001 3 3.14) +(zset 3 3 2.718) +(xset 5.718 3 2.718) +;Value: #f +|# + +;;; AX 3 - Pythagorean triples + +;; In breadth-first we get useful results here. +;; None from depth-first. + +;; AX 3.f - A Pythagorean triple from... + +(define (a-pythagorean-triple-from low) + (let ((i (an-integer-from low))) + (let ((j (an-integer-from i))) + (let ((k (an-integer-from j))) + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + +(define (require p) + (if (not p) (amb))) + +(define (an-integer-from low) + (amb low (an-integer-from (+ low 1)))) + +#| +(with-breadth-first-schedule + (lambda () + (pp (a-pythagorean-triple-from 1)) + (amb))) +(3 4 5) +(6 8 10) +(5 12 13) +(9 12 15) +(8 15 17) +(12 16 20) +(7 24 25) +(15 20 25) +(10 24 26) +(20 21 29) +(18 24 30) +(16 30 34) +(21 28 35) +(12 35 37) +(15 36 39) +(24 32 40) +(9 40 41) +(27 36 45) +(14 48 50) +(30 40 50) +(24 45 51) +(20 48 52) +(28 45 53) +(33 44 55) +(40 42 58) +(36 48 60) +(11 60 61) +(16 63 65) +;Quit! +|# + +;; AX 3.b - A Pythagorean triple between... + +;; For example, for controlling search: + +(define (a-pythagorean-triple-between low high) + (let ((i (an-integer-between low high))) + (let ((j (an-integer-between i high))) + (let ((k (an-integer-between j high))) + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + +(define (an-integer-between low high) + (require (<= low high)) + (amb low + (an-integer-between (+ low 1) high))) + +;; A useful device: + +(define (amb-collect-values result-thunk #!optional limit) + (call-with-current-continuation + (lambda (k) + (let ((values '()) (count 0)) + (fluid-let ((*top-level* (lambda (ignore) (k values))) + (*search-schedule* (empty-search-schedule))) + (let ((value (result-thunk))) + (set! values (cons value values)) + (set! count (+ count 1)) + (if (and (not (default-object? limit)) + (>= count limit)) + (k values)) + (amb))))))) +#| +(with-depth-first-schedule + (lambda () + (let ((mid (amb-collect-values + (lambda () + (a-pythagorean-triple-between 1 20)) + ;; I want only 3, and + ;; I don't want to backtrack into this. + 3))) + (pp (list (a-pythagorean-triple-between 1 10) + mid + (a-pythagorean-triple-between 10 30))) + (amb)))) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (10 24 26)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (10 24 26)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (12 16 20)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (12 16 20)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (15 20 25)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (15 20 25)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (18 24 30)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (18 24 30)) +((3 4 5) ((6 8 10) (5 12 13) (3 4 5)) (20 21 29)) +((6 8 10) ((6 8 10) (5 12 13) (3 4 5)) (20 21 29)) +;Value: #f +|# + +;;;; File: examples.scm + +;;; SICP Section 4.3.2 : Logic Puzzles +;;; +;;; Baker, Cooper, Fletcher, Miller, and Smith live on +;;; different floors of a building that has only five +;;; floors. Baker does not live on the top floor. +;;; Cooper does not live on the bottom floor. Fletcher +;;; does not live on either the top or the bottom +;;; floor. Miller lives on a higher floor than does +;;; Cooper. Smith does not live on a floor adjacent to +;;; Fletcher's. Fletcher does not live on a floor +;;; adjacent to Cooper's. Where does everyone live? +;;; (From Dinesman, 1968) + + +(define (multiple-dwelling) + (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))) + (require + (distinct? + (list baker cooper fletcher miller smith))) + (require (not (= baker 5))) + (require (not (= cooper 1))) + (require (not (= fletcher 5))) + (require (not (= fletcher 1))) + (require (> miller cooper)) + (require + (not (= (abs (- smith fletcher)) 1))) + (require + (not (= (abs (- fletcher cooper)) 1))) + (list (list 'baker baker) + (list 'cooper cooper) + (list 'fletcher fletcher) + (list 'miller miller) + (list 'smith smith)))) + +(define (distinct? items) + (cond ((null? items) #t) + ((null? (cdr items)) #t) + ((member (car items) (cdr items)) #f) + (else (distinct? (cdr items))))) + +#| +(init-amb) +;Value: done + +(with-depth-first-schedule multiple-dwelling) +;Value: ((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1)) + +(amb) +;No more alternatives +|# + +;;; From SICP Section 4.3.2 +;;; Parsing natural language + +(define (parse input) + (amb-set! *unparsed* input) + (let ((sent (parse-sentence))) + (require (null? *unparsed*)) + sent)) + +(define *unparsed* '()) + +(define (parse-sentence) + (let* ((np (parse-noun-phrase)) + (verb (parse-verb-phrase))) + (list 'sentence np verb))) + +(define (parse-noun-phrase) + (define (maybe-extend noun-phrase) + (amb noun-phrase + (maybe-extend + (list 'noun-phrase + noun-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-s-noun-phrase))) + +(define (parse-verb-phrase) + (define (maybe-extend verb-phrase) + (amb verb-phrase + (maybe-extend + (list 'verb-phrase + verb-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-word verbs))) + +(define (parse-s-noun-phrase) + (let* ((article (parse-word articles)) + (noun (parse-word nouns))) + (list 's-noun-phrase article noun))) + +(define (parse-prepositional-phrase) + (let* ((preposition + (parse-word prepositions)) + (np (parse-noun-phrase))) + (list 'prep-phrase preposition np))) + +(define (parse-word word-list) + (require (not (null? *unparsed*))) + (require (memq (car *unparsed*) + (cdr word-list))) + (let ((found-word (car *unparsed*))) + (amb-set! *unparsed* (cdr *unparsed*)) + (list (car word-list) found-word))) + +(define nouns + '(noun student professor cat class)) + +(define verbs + '(verb studies lectures eats sleeps)) + +(define articles + '(article the a)) + +(define prepositions + '(prep for to in by with)) + +#| +(init-amb) +;Value: done + +(pp + (parse + '(the student with the cat sleeps in the class))) + +(sentence + (noun-phrase + (s-noun-phrase (article the) (noun student)) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat)))) + (verb-phrase + (verb sleeps) + (prep-phrase (prep in) + (s-noun-phrase (article the) + (noun class))))) +;Unspecified return value + +(amb) +;No more alternatives +|# + +#| +(init-amb) +;Value: done + +(pp + (parse + '(the professor lectures + to the student with the cat))) + +(sentence + (s-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb-phrase + (verb lectures) + (prep-phrase (prep to) + (s-noun-phrase (article the) + (noun student)))) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat))))) +;Unspecified return value + +(amb) + +(sentence + (s-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb lectures) + (prep-phrase + (prep to) + (noun-phrase + (s-noun-phrase (article the) + (noun student)) + (prep-phrase (prep with) + (s-noun-phrase (article the) + (noun cat))))))) +;Unspecified return value + +(amb) +;No more alternatives +|# + +;;;; File: load.scm + +;; Fun with Continuations +(load "funco") + +;; AMB Scheme extensions +(load "ambsch") + +;; AMB Examples from SICP +(load "examples") + +':have-fun! diff --git a/ps07_amb/stack-queue.scm b/ps07_amb/stack-queue.scm new file mode 100644 index 0000000..71f7c0e --- /dev/null +++ b/ps07_amb/stack-queue.scm @@ -0,0 +1,52 @@ +;;;; Simple stack&queue Abstraction + +(declare (usual-integrations)) + +(define-record-type <stack&queue> + (%make-stack&queue front back) + stack&queue? + (front stack&queue-front set-stack&queue-front!) + (back stack&queue-back set-stack&queue-back!)) + + +(define (make-stack&queue) + (%make-stack&queue '() '())) + +(define (stack&queue-empty? stq) + (not (pair? (stack&queue-front stq)))) + +(define (stack&queued? stq item) + (memq item (stack&queue-front stq))) + +(define (push! stq object) + (if (pair? (stack&queue-front stq)) + (set-stack&queue-front! stq + (cons object (stack&queue-front stq))) + (begin + (set-stack&queue-front! stq + (cons object (stack&queue-front stq))) + (set-stack&queue-back! stq + (stack&queue-front stq)))) + unspecific) + +(define (add-to-end! stq object) + (let ((new (cons object '()))) + (if (pair? (stack&queue-back stq)) + (set-cdr! (stack&queue-back stq) new) + (set-stack&queue-front! stq new)) + (set-stack&queue-back! stq new) + unspecific)) + +(define (pop! stq) + (let ((next (stack&queue-front stq))) + (if (not (pair? next)) + (error "Empty stack&queue -- POP")) + (if (pair? (cdr next)) + (set-stack&queue-front! stq (cdr next)) + (begin + (set-stack&queue-front! stq '()) + (set-stack&queue-back! stq '()))) + (car next))) + + + |