Code files that accompany these answers:
utils.ss:
miscellaneous utility functions.
sort-tester.ss:
the various routines for testing sorting.
sorts.ss:
the sorting routines.
cps.ss:
the continuation-passing-style functions.
encap.ss:
the encapsulation functions.
I graded each section (A, B, and C) as a unit, assigning twice the weight to section A that I assigned to the other two sections.
As I've mentioned many times before, I tend to grade your assignments offline (the only time I can find to grade is often when William is asleep at night). Therefore, I expect to see sample runs of your programs and functions. Not all of y ou chose to include such sample runs.
At this point in your careers, you should know enough to comment your code. I was astounded by the number of you who failed to include even a simple comment about what a function did, let alone an explanation of the more complicated parts of your functions.
On a separate note, I would expect you to make sure that your printouts are legible. Many of you created very long lines and didn't check to make sure that those lines weren't truncated. Make sure that you format your output appropriately before turning anything in.
Many of you did surprisingly poorly on this one, even if you already had some experience with Scheme. As I noted in the introduction to the problem, your goal was to test the sorting routine with a wide variety of lists of different sizes. Many of you forgot to try different size lists, and some of you that tried different size forgot the empty list. When it came time to test the various sorting routines that you wrote or copies from elsewhere, many of you neglected to u se your sorting routines.
Surprisingly, many of you wrote a sorts? routine that
simply checked if the resultant list was sorted. Such a routine
will indicate that the following is a correct sorting method.
;;; Sort a list, or at least pretend to sort the list. (define (foosort lst) '(1 2 3))
Some of you also checked whether the length of the two lists was the same. Those of you will find that your check indicates that the following method sorts correctly.
;;; Sort a list, or at least pretend to sort the list. (define (barsort lst) (nints (length lst) 1))
Note that sorting all the permutations of a list of sixteen elements is incredibly time consuming. As my email suggested, going up to length eight (or even five) should be sufficient.
As a contrast, I've also created a function that gives a list of "random" numbers and have used it for some non-tests. Note that Just because we succeed in sorting a random list, it doesn't mean that the sorting mechanism works. I've included a version of quicksort that seems to work on random lists of moderate size, but may fail if there are duplicates in the list.
;;; Generate a list of "random" numbers in the range -1000 to 1000. ;;; Note that (random x) generates a "random" number from 0 to x-1. (define (randlist n) (if (zero? n) nil (cons (- (random 2002) 1000) (randlist (- n 1)))))
Many of you didn't seem very concerned about efficiency. For example,
when writing insert-at, you compared the position to the
length of the list. This means that insert-at always takes
time linear in the size of the list, even when we're inserting at the
beginning.
A.1. Generating Sequences
Write a Scheme function (nints n start) that generates a
list of
n successive integers starting with start.
For example, (nints 4 -2) would produce the list
(-2 -1 0 1).
;;; Compute the first n integers beginning with start. ;;; Precondition: n >= 0 ;;; Postcondition: returns (start start+1 start+2 ... start+n-1) ;;; Strategy: ;;; Base case: if n is 0, return the empty list ;;; Recursive case: compute the n-1 integers starting with start+1 ;;; (that is, (start+1 start+2 ... start+n-1)), and then prepend ;;; start. ;;; Note: ;;; I've used n <= 0 as the base case to handle obnoxious people ;;; who pass in a negative list length. (define (nints n start) (if (<= n 0) nil (cons start (nints (- n 1) (+ start 1)))))
A.2. Generating Copies
Write a Scheme function (ncopies n val) that generates a
list of n copies of val. For example,
(ncopies 4 3) would produce (3 3 3 3).
;;; Generate a list of n copies of val ;;; precondition: n >= 0 ;;; postcondition: returns the list (val val val ... val) such that ;;; length of the list is n (define (ncopies n val) (if (<= n 0) nil (cons val (ncopies (- n 1) val))))
A.3. Generating Stranger Sequences
Write a Scheme function that generates lists that will be sorted differently if textual and numeric comparison routines are used. (For example, a textual comparison routine will generally place 12 before 2; a numeric comparison routine will place 2 before 12.)
;;; Generate a list whose sorting depends on whether comparison is ;;; textual or numerical. Numerical computation is based on the ;;; value of each element. Textual comparison is based on the individual ;;; digits in sequence. For example, since 1 < 2, a textual comparison ;;; routine will place 11 before 2. ;;; ;;; Note: In order to develop a reasonable list, that list will need ;;; to be of length at least three. ;;; Note: Since we're only checking a simple kind of comparison, it ;;; will suffice to generate the list (1 2 11 12 111 112 ...) ;;; Note: It's useful to generate a numerically sorted list (given ;;; that we'll be using this list to test numerical sorting). ;;; Strategy: ;;; We'll alternate between adding 1 and 10^x-1. For example, ;;; we start with 1. We add 1 to get 2. We add 9 (10^1-1) to ;;; get 11. We add 1 to get 12. We add 99 (10^2) to get 111. ;;; Helper: ;;; Our helper function will keep track of the power-of-ten, the ;;; next value to generate, and the next increment to add. (define (weird-list n) (weirdlist-helper n 1 1 10)) (define (weirdlist-helper n val inc tens) (if (<= n 0) nil (let ((nextn (- n 1)) (nextval (+ val inc)) (nextinc (if (= inc 1) (- tens 1) 1)) (nexttens (if (= inc 1) (* tens 10) tens))) (cons val (weirdlist-helper nextn nextval nextinc nexttens)))))
A.4. Generating Compound Sequences
Write a Scheme function that generates lists which have some of the criteria given above. That is, they should include both different numbers and copies of some number (as long as the list has at least three elements).
Note that the requirement was only to have both different numbers and copies of the same number. We didn't need to meet the "sorts differently in textual and numeric comparison" requirement because we'll be using our answer from A.3 for testing that anyway.
Since we already have a way of testing many copies of one number, I designed mine so that if the list is sufficiently long, we get many copies of many numbers.
For the implementation of this one, we'll define a local helper function
(rather than an external one). Since the helper function will be
recursive, we need to use letrec rather than
let.
;;; Generate a list that includes both duplicates and different numbers. ;;; Make it an ordered list so that it's easy to check if we've sorted ;;; it correctly. ;;; Alternate adding 3 and 0 to the last element added. (define (dupe-and-diff n) (letrec ((helper (lambda (n val inc) (if (<= n 0) nil (cons val (helper (- n 1) (+ val inc) (if (= inc 0) 3 0))))))) (helper n 1 0)))
A.5. Generating Permutations
Write a recursive Scheme function, (permutations lst), that
generates a list of all the permutations of lst.
Since we're writing this recursively, we need to consider what we can recurse on. The most likely candidate is the list itself. So, to compute the permutations of a list, l, we
The recursive part is easy. How do we reinsert the car into a single
permutation? We need to make a list of lists, with the car inserted at
each place. We'll call that function insert-everywhere.
When writing that function, it is likely that we'll benefit from
insert-at, which inserts an element at a particular place.
We'll also need to write that function.
How do we reinsert the car into every permutation? Using
map. But that gives us a list of lists of lists.
We then need to concatenate those lists. We can just apply
append to that list.
Are there more elegant ways to do this? Certainly. This is just one strategy.
;;; Compute all permutations of a list. ;;; Design: ;;; If the list is empty, then the list of the empty list gives all of its ;;; permutations (the same is true for the single-element list, but ;;; we'll handle it in the recursive case). ;;; Otherwise, compute all the permutations of the cdr, insert the ;;; car "everywhere" in each permutation, and join the results ;;; together. (define (permutations lst) (if (= (length lst) 0) (list nil) (let ((subperms (permutations (cdr lst))) (first (car lst))) (apply append (map (lambda (sub) (insert-everywhere first sub)) subperms))))))
Note that this function actually generates a superset of the
permutations in that the same list can appear multiple times within the
list of permutations. For example, if we asked for the permutations
of (1 1 2), we'd get
( (1 1 2) (1 1 2) (1 2 1) (1 2 1) (2 1 1) (2 1 1) )
We can solve this by removing duplicates from the resultant list. How to do so is left as an exercise for the reader.
Now, how can we write insert-everywhere? One way is
recursively, using a helper function that counts the positions at
which we've insert the element. I suggested such a solution to
almost everyone who asked for help. However, it's also possible
to do this using map. Basically, we make a list of
the positions at which to insert an element, and then map an
appropriate call to insert-at onto that list.
;;; Given an element and a list, create a list of lists such that ;;; each element of the list-of-lists consists of the original ;;; list with the element inserted at some position. (define (insert-everywhere elt lst) (map (lambda (pos) (insert-at pos elt lst)) (nints (+ 1 (length lst)) 1)))
How can we write insert-at? This seems easiest to
write recursively. We'll recurse on the position to insert and
the list itself. That is, to insert at the nth position in
list l, we can insert at the n-1st position in the cdr of l (and
then rebuild, due to the way Scheme handles lists).
;;; Given a position, an element, and a list, insert the element ;;; at the appropriate position of the list. For example, ;;; Precondition: 1 <= pos <= (length lst) + 1 ;;; (insert-at 1 'a '(b c d)) => (a b c d) ;;; (insert-at 2 'a '(b c d)) => (b a c d) ;;; (insert-at 3 'a '(b c d)) => (b c a d) ;;; (insert-at 4 'a '(b c d)) => (b c d a) ;;; (insert-at 4 'a '(b c d)) => BOOM (define (insert-at pos elt lst) (if (= pos 1) (cons elt lst) (cons (car lst) (insert-at (- pos 1) elt (cdr lst)))))
A.6. Developing a Testing Predicate
Using the pieces developed above, develop a predicate,
(sorts? fun) that returns true if fun
appears to sort lists and false otherwise.
As a strategy, we'll develop a function,
failed-permutations, that, given a sorting function and a
sorted list, lists all the permutations of that list that the sorting
function fails to sort. As a helper function for that, we'll develop a
predicate sorts-perm? that checks whether a sorting
function sorts one permutation.
;;; Given a sorting function, f, a sorted list, l, and a permutation of ;;; that list, p, determine if f correctly sorts l. (define (sorts-perm? f l p) (equal? l (f p)))
And a quick test (not a great one, but I'm pretty sure that it's correct given it's short length).
> (sorts-perm? id '(1 2 3) '(1 2 3)) #t > (sorts-perm? id '(1 2 3) '(2 1 3)) #f
In the next function will help to use select. Since I've
forgotten the name of the built-in one, I've defined my own.
;;; Select all elements of a list that meet a predicate. (define (select pred lst) (if (null? lst) nil (let ((first (car lst)) (rest (select pred (cdr lst)))) (if (pred first) (cons first rest) rest))))
Now, we can move on to checking whether the sorting function works with with all permutations. Note that it might be easier to return true or false, but I decided I wanted a list of when sorting failed.
;;; Given a sorting function, sortfun, and a sorted list, l, determines ;;; all permutations of l that the sorting function fails to sort. ;;; If the sorts every function successfully, return nil. (define (failed-permutations sortfun l) (select (lambda (perm) (not (sorts-perm? sortfun l perm))) (permutations l)))
We can easily turn this into a predicate with
;;; Given a sorting function, sortfun, and a sorted list, l, determine ;;; if sortfun correctly sorts all permutations of l. (define (sorts-all-permutations? sortfun l) (null? (failed-permutations sortfun l)))
We want to then check different size lists. We'll write another
helper function, sorts-up-to? which returns true if
the sorting method seems to sort list of up to size n.
;;; Given a sorting function, sort, and a number, n, determine ;;; whether the sorting function seems to sort all lists of size ;;; up to n. (define (sorts-up-to? sort n) (if (<= n 0) (equal? nil (sort nil)) (and ;;; Try lists of positive numbers (sorts-all-permutations? sort (nints n 1)) ;;; Try lists of negative and positive numbers (sorts-all-permutations? sort (nints n (- 0 (round (/ n 2))))) ;;; Try lists of identical numbers (sorts-all-permutations? sort (ncopies n 0)) (sorts-all-permutations? sort (ncopies n 1)) ;;; Try the weirdo lists that are sorted differently ;;; if textual sorting is used (sorts-all-permutations? sort (weird-list n)) ;;; Try our standard list with both duplicate and ;;; different elements. (sorts-all-permutations? sort (dupe-and-diff n)) ;;; And recurse (sorts-up-to? sort (- n 1)))))
If we so desire, we could also turn this into something that lists all the lists that we fail to sort. Here's a start
;;; Build a list of lists of up to size n that our function fails to sort. ;;; Certainly not guaranteed to provide all such lists, if there are ;;; some that the function fails to sort, is likely to produce at ;;; least one such list. (define (fails-to-sort sortfun n) (if (= n 0) (failed-permutations sortfun nil) (append (failed-permutations sortfun (nints n 1)) (failed-permutations sortfun (ncopies n 1)) (fails-to-sort sortfun (- n 1)))))
Finally, our sorts? function can be written using one of these.
If we want to test lists up to size 7, we will use
;;; See if our sorting function seems to sort lists up to size 7. (define (sorts? sortfun) (sorts-up-to? sortfun 7))
A.7. Developing a Quicksort Function
Write a function, (quicksort lst), that computes a sorted
version of lst using the quicksort algorithm. Feel free
to reuse code from class (provided you cite it appropriately). Run your
testing predicate on your sorting routine and report the results.
Some of you used the one from my notes, some the one we wrote in class (which was slightly different), some one by our textbook author, and some other ones. I think a few of you may have decided to rewrite this one. Rather than copying and pasting from my notes, I've rewritten the procedure from scratch.
;;; Sort a list of numbers using the legendary quick sort procedure. ;;; We pick a pivot, split the list into three parts: smaller than the ;;; pivot, equal to the pivot, and greater than the pivot. We then ;;; recursively sort the smaller and lesser sublists and join the results ;;; together. (define (quicksort lst) (if (null? lst) nil (let ((pivot (car lst))) (append (quicksort (select (lambda (x) (< x pivot)) lst)) (select (lambda (x) (= x pivot)) lst) (quicksort (select (lambda (x) (> x pivot)) lst))))))
We can now run our testing predicate. Note that using length-8 lists took me less than a minute, so I'm not sure why some of you reported that you need to stop with shorter lists.
> (sorts-up-to? quicksort 8) #t
Here's a variant that may forget some elements.
(define (qsort lst) (if (null? lst) nil (let ((pivot (car lst))) (append (quicksort (select (lambda (x) (< x pivot)) lst)) (list pivot) (quicksort (select (lambda (x) (> x pivot)) lst))))))
And some testing. Note that using a "standard" testing style ("Oh boy, it seems to sort a list of random numbers"), this seems to work fine. However, if we do structured testing of the sorting method, it becomes clear that there are some simple length-three lists that it fails to sort.
> (sorts-all-permutations? qsort (nints -3 7)) #t > (qsort (randlist 20)) (-931 -898 -833 -801 -692 -690 -489 -361 -352 -215 -212 -89 55 318 521 647 723 756 806 902) > (sorts-up-to? qsort 3) #f
A.8. Developing a Mergesort Function
Write a function, (mergesort lst), that computes a sorted
version of lst using the mergesort algorithm. Feel free
to reuse code from class (provided you cite it appropriately). Run your
testing predicate on your sorting routine and report the results.
;;; Sort a list of numbers using the legendary merge sort procedure. ;;; In the recursive version of this procedure, we split the list in ;;; half, sort the two halves, and then merge the results. I've ;;; decided that it's okay to rearrange the list when splitting. (define (mergesort lst) ; First base case: empty list (if (null? lst) nil ; Second base case: single element list (if (null? (cdr lst)) lst ; Recursive case: split, sort the two halves, then merge (let ((halves (split lst))) (merge (mergesort (car halves)) (mergesort (cdr halves)))))))
For this to work, we need both merge and split.
split breaks a list into a dotted pair of more-or-less equal
size lists by stepping through the list and prepending to one of two
lists. It is probably not the most elegant way to do things, but hey,
that's life.
;;; Split a list into two lists of approximately the same size. ;;; Postcondition: each element in the original list appears exactly ;;; once in one of the two lists ;;; Postcondition: the two lists differ in length by at most 1. ;;; Returns: a list whose car is the first list and ;;; whose cdr is the second list. ;;; Note: May rearrange the elements. (define (split lst) (letrec ((splithelper (lambda (lst first second add-to-first) (if (null? lst) (cons first second) (if add-to-first (splithelper (cdr lst) (cons (car lst) first) second #f) (splithelper (cdr lst) first (cons (car lst) second) #t) ) ; if add-to-first ) ; if the lst is null ))) ;;; variables in the letrec (splithelper lst nil nil #t) ) ; letrec ) ; define split
Now we're ready to merge.
;;; Merge two sorted lists into a sorted list (define (merge first second) (if (null? first) second (if (null? second) first (if (< (car first) (car second)) (cons (car first) (merge (cdr first) second)) (cons (car second) (merge first (cdr second)))))))
And we can run our testing predicate.
> (sorts-up-to? mergesort 8) #t
Just for the fun of it, I implemented a non-working sorting procedure very similar to merge sort. Here it is.
;;; Sort a list using the legendary mrgsort procedure that splits ;;; the list in two, sorts the first half, and merges them together. (define (mrgsort lst) (if (null? lst) nil (if (null? (cdr lst)) lst (let ((halves (split lst))) (merge (mrgsort (car halves)) (cdr halves))))))
Here's some manual testing and then some automated testing.
> (mrgsort '(1 2 3)) (1 2 3) > (mrgsort '(3 2 1)) (1 2 3) > (mrgsort '(2 1 3)) (1 2 3) > (mrgsort '(1 1 1)) (1 1 1) > (sorts-up-to? mrgsort 0) #t > (sorts-up-to? mrgsort 1) #t > (sorts-up-to? mrgsort 2) #t > (sorts-up-to? mrgsort 3) #t > (sorts-up-to? mrgsort 4) #f > (fails-to-sort mrgsort 4) ((1 2 3 4) (2 1 3 4) (2 3 1 4) (1 3 2 4) (3 1 2 4) (3 2 1 4) (3 1 4 2) ...
Again, note that simple manual testing (and even some simple automated testing) doesn't reveal the errors in the method. However, testing on larger lists reveals that there are errors in the method.
A.8. Developing an Insertion Sort Function
Write a function, (insertion-sort lst), that computes a sorted
version of lst using the insertion sort algorithm. Feel free
to reuse code from class (provided you cite it appropriately). Run your
testing predicate on your sorting routine and report the results.
;;; Sort a list by using the insertion sort procedure. We sort the ;;; cdr of the list and then insert the element in the appropriate ;;; place in that list. (define (insertion-sort lst) ; Base case: empty list (if (null? lst) lst ; Recursive case: sort the cdr and insert (insert (car lst) (insertion-sort (cdr lst)))))
Now we just have to write insert.
;;; Insert an element into the appropriate place in a sorted list. (define (insert elt lst) ; Base case one, empty list: create a new list containing the element (if (null? lst) (list elt) ; Base case two, element falls before the first element in the list: ; cons 'em together (if (< elt (car lst)) (cons elt lst) ; Recursive case: insert into remainder of list (cons (car lst) (insert elt (cdr lst))))))
And we're ready to test.
> (sorts-up-to? insertion-sort 8) #t
Wasn't that thrilling?
In general, your answers to these questions were barely acceptable. Most of you didn't worry about the "real" current continuation for B.2, even though I speccifically stated "You should use the current continuation as cont". Few of you did any significant testing of your expressions for part B.2. (particularly with errors). Almost no one ensured that an error in an "inner" part of the expression resulted in the rest of the expression being ignored. Only one of you (I think) tried the extra credit.
Note that weak testing presumably leads to weak understanding. Part of the purpose of continuations is to escape from bad computations. If you didn't use a reasonable failure continuation, it wasn't possible to escape. Note that in my examples, we clearly escaped from the outer computation.
B.1: Continuation-passing Mathematical Operations
Write Scheme functions (cadd a b cont failcont),
cmult, csub, cdiv and
csqrt. Each of these functions takes some values and two
continuations as parameters. The functions are expected to compute the
appropriate function of the initial values (e.g., addition for
cadd, square root for csqrtcont to the result of the function if successful. If the
operation fails, they should apply failcont to 0.
I've decided to modify the functions slightly and put the failure continuation before the success continuation. Like some of you, I've decided to call the failure continuation on an error message, rather than on the value 0. Although the functions are similar, I've used different strategies for implementing them to suggest alternatives.
;;; Add two values and call an appropriate continuation on the result. (define (cadd a b failcont succcont) (if (and (number? a) (number? b)) (succcont (+ a b)) (failcont "Attempted to add non-numeric values")))
;;; Subtract one value from another and call an appropriate continuation ;;; on the result. (define (csub a b failcont succcont) (if (and (number? a) (number? b)) (succcont (- a b)) (failcont "Attempted to subtract non-numeric values")))
;;; Multiply two values and call an appropriate continuation on the ;;; result. (define (cmult a b failcont succcont) (if (not (number? a)) (failcont "Attempted to multiply a non-numeric value") (if (not (number? b)) (failcont "Attempted to multiply a non-nuemric value") (succcont (* a b)))))
;;; Divide one value by another and call an appropriate continuation ;;; on the result. (define (cdiv numerator divisor failcont succcont) (if (not (number? numerator)) (failcont "Attempted to divide a non-numeric value") (if (not (number? divisor)) (failcont "Attempted to divide by a non-nuemric value") (if (zero? divisor) (failcont "Attempted to divide by 0") (succcont (/ numerator divisor))))))
;;; Compute the square root of a nonnegative number. (define (csqurt a failcont succcont) (if (not (number? a)) (failcont "Can't compute the square root of a non-numeric value") (if (< a 0) (failcont "Can't compute the square root of a negative number") (succcont (sqrt a)))))
B.2: Using continuations
Convert each of the following to continuation-passing style, using your
functions from B.1 above. You should use the current
continuation as cont and use a function that prints an
error message as failcont.
We'll use a helper function that build a failure continuation out of a normal continuation. The failure continuation prints an error and then returns 0.
;;; Given a continuation, make a new continuation that reads and ;;; prints an error message and then calls the original continuation ;;; on 0. (define (makefail cont) (lambda (err) (display (string-append "Failure: " err)) (newline) (cont 0)))
Here's another helper function that calls any continuation-passing style function whose core is binary using arguments A and B.
;;; Call a CPS function with parameters A and B, using the current "real" ;;; continuation. Note that A and B have to be defined. (define (call-cps cps) (call/cc (lambda (cont) (cps A B (makefail cont) cont))))
Now we're ready to go.
; (+ A B) > (call-cps cadd) Error: variable b is not bound. > (define a 1) > (define b 2) > (call-cps cadd) 3 > (define a 'a) > (call-cps cadd) Failure: Attempted to add non-numeric values 0 ; (/ A B) > (define a 10) > (call-cps cdiv) 5 > (define b 0) > (call-cps cdiv) Failure: Attempted to divide by 0 0 ; (+ A (/ A B)) > (call/cc (lambda (cont) (cdiv A B (makefail cont) (lambda (result) (cadd A result (makefail cont) cont))))) > (define b 5) > (call/cc (lambda (cont) (cdiv A B (makefail cont) (lambda (result) (cadd A result (makefail cont) cont))))) 12 > (define a 'foo) > (call/cc (lambda (cont) (cdiv A B (makefail cont) (lambda (result) (cadd A result (makefail cont) cont))))) Failure: Attempted to divide a non-numeric value 0 ; (+ (/ A B) A) > (call/cc (lambda (cont) (cdiv A B (makefail cont) (lambda (result) (cadd result A (makefail cont) cont))))) Failure: Attempted to divide a non-numeric value 0 > (define B 4) 25/2 ; (/ (+ A B) B) > (define A 10) > (define B 0) > (call/cc (lambda(cont) (cadd A B (makefail cont) (lambda (result) (cdiv result B (makefail cont) cont))))) Failure: Attempted to divide by 0 0
B.3: Computing continuations
This problem is optional. It serves as extra credit for the assignment.
Write a function, (cps expression), that converts an expression
in Scheme prefix format to continuation-passing style. In effect, your
function will provide answers for the previous question.
Since this was optional, I did not develop a solution.
In this part of the assignment, we were building what I called encapsulated values, values and expressions that are kept in an unevaluated state by putting them in a lambda abstraction. The goal was to help you think about infinite lists. Again, most of you had correct answers, but few tried anything particularly interesting.
C.1. Unencapsulation
Write a function, (demand encapsulated), that extracts
an encapsulated value from a lambda abstraction.
;;; Demand the value associated with an encapsulated value by applying ;;; the function. (define (demand encapsulated) (encapsulated))
While this works fine for something like
it doesn't work so well for(demand (lambda () 2))
as we can see in(demand 2)
> (demand (lambda () 5)) 5 > (demand 5) Error: attempt to apply non-procedure 5.
We can make our solution a little bit better by using the
the procedure? predicate to check whether the
argument is a procedure. If so, we apply it. If not, we use
the current value. This won't work if the argument is a
non-nullary procedure, but that's the best we can do.
;;; Demand an encapsulated value, making sure that it seems to ;;; be encapsulated. (define (demand encapsulated) (if (procedure? encapsulated) (encapsulated) encapsulated))
This works with both encapsulated and nonencapsulated values. As we can
see from the third example below, the encapsulation really does work (as
"Hello" is not printed until we demand the value of
encap). However, demand does fail on
functions which take arguments, as we see when we attempt to apply it to
car
> (demand 5) 5 > (demand (lambda () 5)) 5 > (define encap (lambda () (display "Hello") (newline) 5)) > (demand encap) Hello 5 > (demand car) Error: incorrect number of arguments to #.
C.2. List Unencapsulation
Write functions demandcar and demandcdr, that
extract the actual car and cdr of an encapsulated
list (a list in which both car and cdr are encapsulated).
;;; Compose two functions. (define (compose f g) (lambda (x) (f (g x)))) ;;; Demand the car of a list. (define demandcar (compose demand car)) ;;; Demand the cdr of a list. (define demandcdr (compose demand cdr))
C.3. Infinite Lists
Write a function, (intsfrom n), that creates an encapsulated
list of all the integers from n to infinity. Using an appropriate
variant of the firstn function above, test your function.
;;; Create an encapsulated list of all the integers starting with n. (define (intsfrom n) (cons (lambda () n) (lambda () (intsfrom (+ n 1)))))
;;; Grab the first n elements of an encapsulated list. (define (firstn n lst) (if (= n 0) nil (cons (demandcar lst) (firstn (- n 1) (demandcdr lst)))))
> (firstn 10 (intsfrom -4)) (-4 -3 -2 -1 0 1 2 3 4 5)
C.4. Other Applications
Come up with some other interesting infinite list and demonstrate its use
with firstn.
We'll do the Fibonacci numbers. It seems best to do this by using a helper function that takes the previous two Fibonacci numbers as arguments
;;; Generate the list of Fibonacci numbers. (define (fibs) (fibs-helper 1 1)) ;;; Generate the list of Fibonacci numbers, given the first two ;;; numbers in the list. (define (fibs-helper alpha beta) (cons (lambda () alpha) (lambda () (fibs-helper beta (+ alpha beta)))))
And a quick demonstration that it works
> (firstn 10 fibs) (1 1 2 3 5 8 13 21 34 55)
Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
Source text last modified Mon Apr 20 10:23:12 1998.
This page generated on Mon Apr 20 11:03:23 1998 by SiteWeaver.
Contact our webmaster at rebelsky@math.grin.edu