Announcement: the reading for next week is Chapter 3 and Appendix B of EoPL.
;; A ListofNum is one of: ;; - '() ;; - (cons Number ListofNum) ;; A ListofStr is one of: ;; - '() ;; - (cons String ListofStr)
This is a common pattern; we can see it again here:
;; A ListofSym is one of: ;; - '() ;; - (cons Symbol ListofSym)
A common adage in Computer Science is D.R.Y.: "Don't Repeat Yourself"
Rather than write several expressions that all look similar, it is better figure out how to abstract over the expressions, and make a single definition that can be reused. (Sound familiar?)
When you see two expressions that look similar and decide that you want to try to factor out a reusable abstraction of them, start by identifying what is different between them. Then parameterize over those components.
Here is a way to do this with data definitions:
;; A Listof[X] is one of: ;; - '() ;; - (cons X Listof[X] )
Different people (and languages) use different notations
for this idea, e.g. "[Listof X]
", or
"α list
." But the particular syntax of how you
notate such parameterization and instantiation
is not nearly as important as the idea of such abstraction.
You can test these abstractions, at least in your head. A crucial test of a parameteric data definition is to check that you can get back to what you had before.
So in the case of Listof[X]
, we should test
it by making sure we can use it to again define classes
corresponding to ListofNum
and ListofStr
.
ListofNum
back by plugging Number
in for X
:
;; A Listof[Number] is one of: ;; - '() ;; - (cons Number Listof[Number] )
ListofStr
back by plugging
String
in for X
:
;; A Listof[String] is one of: ;; - '() ;; - (cons String Listof[String] )
Another example:
;; A Toy is one of: ;; - Symbol ;; - (cons Integer (cons Toy (cons Toy '()))) ;; A Game is one of: ;; - Integer ;; - (cons Integer (cons Game (cons Game '())))
Ex 1. What common abstraction can we make from these definitions?
Write down a parametric data definition for the abstraction.
Test it by plugging in appropriate arguments to
get classes equivalent to Toy
and Game
.
Sample solution 1:
;; A Funof[X] is one of: ;; - X ;; - (cons Integer (cons Funof[X] (cons Funof[X] '())))
Sample solution 2:
;; A Playof[X,Y] is one of: ;; - X ;; - (cons Y (cons Playof[X,Y] (cons Playof[X,Y] '())))
Ex 2. Can you use your definition from the previous exercise to describe a class equivalent to the following:
;; A Puzzle is one of: ;; - String ;; - (cons String (cons Puzzle (cons Puzzle '())))Why or why not?
Sample solution 1: no, because there is no class of data
to plug in for X
in Funof[X]
that
will yield an equivalent definition.
The second clause of the definition of Funof[X]
,
for any X
, does not match Puzzle's second clause,
Sample solution 2: yes: Playof[String,String]
describes the same class of data as Puzzle
.
;; An NeLof[X] is one of: ;; - (cons X '()) ;; - (cons X NeLof[X] )
What are examples of this kind of data?
What kind of data does this remind you of? How are they related?
What is the template for processing this kind of data?
(This section corresponds to section 1.3 of the reading in EoPL.)
Sometimes a function needs to track information about where it has been before it can get to where it wants to go.
rev-list
, which takes a list and returns a new list
with all of the elements from the input, but in reverse order.
Examples: what should rev-list
produce for the following inputs:
(list)
(list 'a 'b 'c)
(list 1 3 2 4 3)
(list "time" "and" "time" "again")
Now, as for the function itself:
let us develop this together using our standard techniques.
(interactive development of a function.)
;; rev-list-help : Listof[X] Listof[X] -> Listof[X] ;; usage: (rev-list-help (j k .. y z) (i .. b a)) is (z y .. k j i .. b a) (define rev-list-help (lambda (lst rev-of-before) (cond ((null? lst) rev-of-before) (else (rev-list-help (cdr lst) (cons (car lst) rev-of-before)))))) (equal? (rev-list-help '() '()) '()) (equal? (rev-list-help '(j) '(a)) '(j a)) (equal? (rev-list-help '(j k) '(a)) '(k j a)) (equal? (rev-list-help '(j k) '(b a)) '(k j b a)) (equal? (rev-list-help '(i j k) '(c b a)) '(k j i c b a)) ;; rev-list : Listof[X] -> Listof[X] (define rev-list (lambda (lst) (rev-list-help lst '()))) (equal? (rev-list (list)) '()) (equal? (rev-list (list 'a 'b 'c)) '(c b a)) (equal? (rev-list (list 1 3 2 4 3)) '(3 4 2 3 1)) (equal? (rev-list (list "time" "and" "time" "again")) '("again" "time" "and" "time"))
Lets work on a version of longest-runs
(a function from MP1), that works on
Listof[Integer]
(instead of the IntegerSequence
data definition described in MP1).
Examples: what should longest-runs
produce for the following
inputs:
(list)
(list 1 1 1)
(list 3 3 3 2 2 2 2 1 1 1)
(list 1 1 1 2 2 3 3 3)
Now, as for the function itself:
The following exercises are designed to be easiest to implement if you write a helper procedure with a context parameter. So as you work through each problem, keep in mind the question of what state would be most useful to carry along as your function traverses its input.
Ex 3. Develop the function make-palindrome
,
which accepts a NeLof[Symbol]
and constructs
a palindrome by mirroring the list around the last item.
For example, (make-palindrome '(a b c))
(a b c b a)
rev-list
or reverse
function in your answer.)
Remember to start by writing some tests!
(This was a tricky problem, and not well motivated; a bad combination)
Sample solution:
Examples:
(make-palindrome '(a)) is '(a)
(make-palindrome '(b a)) is '(b a b)
(make-palindrome '(b a c)) is '(b a c a b)
If we track the list of symbols we have seen so far, then when we traverse
the list and get to the end (cons Symbol '())
,
we will have a list of all of the symbols we saw before the last
one, in reverse order. That corresponds to a suffix of the
desired result. At the same time, the remaining prefix of the
desired result corresponds to the original input.
So if we build up a new copy
of the input NeLof[X]
, but this time cons'ing it up
on top of the accumulated structure,
we should get the desired result.
;; make-palindrome-helper : NeLof[X] Listof[X] -> NeLof[X] ;; usage: (make-palindrome-helper (j k ... y z) (i ... b a)) ;; is (j k ... y z y ... k j i ... b a) (define make-palindrome-helper (lambda (nel rev) (cond ((null? (cdr nel)) (cons (car nel) rev)) (else (cons (car nel) (make-palindrome-helper (cdr nel) (cons (car nel) rev))))))) (equal? (make-palindrome-helper '(x) '()) '(x)) (equal? (make-palindrome-helper '(x) '(d)) '(x d)) (equal? (make-palindrome-helper '(x y) '()) '(x y x)) (equal? (make-palindrome-helper '(x y) '(d)) '(x y x d)) ;; make-palindrome : NeLof[X] -> NeLof[X] ;; usage: (make-palindrome (a b c .. i j)) is (a b c .. i j i .. c b a) (define make-palindrome (lambda (nel) (make-palindrome-helper nel '()))) (equal? (make-palindrome '(a)) '(a)) (equal? (make-palindrome '(b a)) '(b a b)) (equal? (make-palindrome '(b a c)) '(b a c a b))Another acceptable solution that is easier to understand:
;; make-palindrome : NeLof[X] -> NeLof[X] ;; usage: (make-palindrome (a b c .. i j)) is (a b c .. i j i .. c b a) (define make-palindrome (lambda (nel) (let ((rev-nel (rev-list nel))) (append nel (cdr rev-nel)))))
Ex 4. Develop the function to-ten
, which consumes
a list of digits (numbers between 0 and 9) and produces the
corresponding number. The first digit is the most significant
one. Examples: (to-ten (list 1 0 2))
(to-ten (list 2 1))
Remember to start by writing some tests!
This may also be hard, but it is much better motivated. The key to doing this problem is seeing that as you encounter each digit, you can keep a running tally approximating the value of the number you'll get at the end.
;; to-ten-helper : Listof[Digit] Number -> Number ;; usage: (to-ten-helper (d_k d_k-1 ... d_1 d_0) num) ;; is num*10^k+1 + d_k*10^k + d_k-1*10^k-1 + ... + d_1*10 + d_0 (define to-ten-helper (lambda (tally digits) (cond ((null? digits) tally) (else (to-ten-helper (+ (car digits) (* 10 tally)) (cdr digits)))))) (equal? (to-ten-helper 14 '()) 14) (equal? (to-ten-helper 14 '(2)) 142) (equal? (to-ten-helper 14 '(2 1)) 1421) ;; to-ten : Listof[Digit] -> Number ;; usage: (to-ten-helper (a b c d ... k)) is the number abcd...k, ;; where a through k are the digits making up the result. (define to-ten (lambda (digits) (to-ten-helper 0 digits))) (equal? (to-ten '()) 0) (equal? (to-ten '(1 0 2)) 102) (equal? (to-ten '(2 1)) 21)
Here is a data definition for representing HTML-like documents:
;; An AttrList is one of: ;; - '() ;; - (cons (list Symbol String) AttrList) ;; An X-exp is one of: ;; - String ;; - (cons Symbol (cons AttrList X-list)) ;; An X-list is one of: ;; - '() ;; - (cons X-exp X-list) ;; Examples: ;; "Hello" ;; '(b () "Hi" (em () "ther") "e") ;; represents Hithere ;; '(strike () "and " (a ((href "http://www.google.com")) "Google")) ;; representsandGoogle
What does the template for processing an X-exp
look like?
We start by noting that in addition to the self-references in
AttrList
and X-list
, there are
cross-references, where the definition for X-list
refers to X-exp
, and the definition for
X-exp
refers to AttrList
and X-list
This is an example of a data definition that exhibits mutual recursion, because there is a cycle in the cross-references between the data definitions. So the rule of thumb for developing a template for this is the following: when we make a function to process data from mutually recursive definitions, we make a separate function for each definition.
;; process-x-exp : X-exp -> ??? ;; usage: (process-x-exp an-xexp) does what??? (define process-x-exp (lambda (xe) (cond ((string? xe) ...) (else ... (car xe) ... ; tag ... (car (cdr xe)) ... ; attribute list ... (process-x-list (cdr (cdr xe))) ... ; remaining x-exp's. )))) ;; process-x-list : X-list -> ??? ;; usage: (process-x-list an-xlist) does what??? (define process-x-list (lambda (xl) (cond ((null? xl) ...) (else ... (process-x-exp (car xl)) ... ; x-exp ... (process-x-list (cdr xl)) ... ; remaining x-exp's ))))
Here's another example, based on the grammar for S-list from the book,
(but extended with Number
s).
;; An Nus-list is one of: ;; - '() ;; - (cons Nus-exp Nus-list) ;; An Nus-exp is one of: ;; - Number ;; - Symbol ;; - Nus-list
Ex 5. What does the template/skeleton of a function to process this data look like? Get a lab helper to approve your template/skeleton before moving onto the next problem.
Sample solution:
;; f : Nus-list -> ??? ;; usage: ??? (define f (lambda (nusl) (cond ((null? nusl) ...) (else ... (g (car nusl)) ... ... (f (cdr nusl)) ...)))) ;; g : Nus-exp -> ??? ;; usage: ??? (define g (lambda (nuse) (cond ((number? nuse) ...) ((symbol? nuse) ...) (else ... (f nuse) ...))))
Ex 6. Implement symbols-of
, which
produces a list of the symbols in an argument Nus-list
.
Remember to start by writing some tests!
Sample solution:
;; symbols-of : Nus-list -> Listof[Symbol] ;; usage: (symbols-of nusl) returns all symbols in nusl (define symbols-of (lambda (nusl) (cond ((null? nusl) '()) (else (append (symbols-of-exp (car nusl)) (symbols-of (cdr nusl))))))) ;; symbols-of-exp : Nus-exp -> Listof[Symbol] ;; usage: (symbols-of-exp nuse) returns all symbols in nuse (define symbols-of-exp (lambda (nuse) (cond ((number? nuse) '()) ((symbol? nuse) (list nuse)) (else (symbols-of nuse))))) (equal? (symbols-of '()) '()) (equal? (symbols-of '(a)) '(a)) (equal? (symbols-of '(a b)) '(a b)) (equal? (symbols-of '(a 1 b)) '(a b)) (equal? (symbols-of '(a (1) b)) '(a b)) (equal? (symbols-of '((a) (1) b)) '(a b)) (equal? (symbols-of '((a b) (1 c) b)) '(a b c b))
Ex 7. Implement reverse-nuslist
, which
reverses the tree structure of its argument Nus-list
.
For example, the reverse of ((z a) ((b a) c) d)
(d (c (a b)) (a z))
Remember to start by writing some tests!
Sample solution 1:
;; put-at-end : X Listof[X] -> Listof[X] ;; usage: (put-at-end x lx) makes a copy of lx with x on the end of it. (define put-at-end (lambda (x lx) ;; a pattern for putting something at list's END (append lx (list x)))) (equal? (put-at-end 'a '()) '(a)) (equal? (put-at-end 'a '(y)) '(y a)) (equal? (put-at-end 'a '(y z)) '(y z a)) ;; reverse-nuslist : Nus-list -> Nus-list ;; usage: (reverse-nuslist nl) is deeply reversed nl (define reverse-nuslist (lambda (nl) (cond ((null? nl) '()) (else (put-at-end (reverse-nusexp (car nl)) (reverse-nuslist (cdr nl))))))) ;; reverse-nusexp : Nus-exp -> Nus-exp ;; usage: (reverse-nusexp ne) is deeply reversed ne (define reverse-nusexp (lambda (ne) (cond ((number? ne) ne) ((symbol? ne) ne) (else (reverse-nuslist-help ne '()))))) (equal? (reverse-nuslist '()) '()) (equal? (reverse-nuslist '(a)) '(a)) (equal? (reverse-nuslist '(a b)) '(b a)) (equal? (reverse-nuslist '(a 1 b)) '(b 1 a)) (equal? (reverse-nuslist '(a (1) b)) '(b (1) a)) (equal? (reverse-nuslist '((a) (1) b)) '(b (1) (a))) (equal? (reverse-nuslist '((a b) (1 c) b)) '(b (c 1) (b a)))
Sample solution 2: This problem is interesting because you can adopt some of the lessons about accumulators above.
;; reverse-nuslist-help : Nus-list Nus-list -> Nus-list ;; usage: (reverse-nuslist nl rn) is deeply reversed nl concatenated onto rn (define reverse-nuslist-help (lambda (nl rev-so-far) (cond ((null? nl) rev-so-far) (else (reverse-nuslist-help (cdr nl) (cons (reverse-nusexp (car nl)) rev-so-far)))))) ;; reverse-nusexp : Nus-exp -> Nus-exp ;; usage: (reverse-nusexp ne) is deeply reversed ne (define reverse-nusexp (lambda (ne) (cond ((number? ne) ne) ((symbol? ne) ne) (else (reverse-nuslist-help ne '()))))) ;; reverse-nuslist : Nus-list -> Nus-list ;; usage: (reverse-nuslist nl) is deeply reversed nl (define reverse-nuslist (lambda (nl) (reverse-nuslist-help nl '()))) (equal? (reverse-nuslist '()) '()) (equal? (reverse-nuslist '(a)) '(a)) (equal? (reverse-nuslist '(a b)) '(b a)) (equal? (reverse-nuslist '(a 1 b)) '(b 1 a)) (equal? (reverse-nuslist '(a (1) b)) '(b (1) a)) (equal? (reverse-nuslist '((a) (1) b)) '(b (1) (a))) (equal? (reverse-nuslist '((a b) (1 c) b)) '(b (c 1) (b a)))
In Scheme, functions are first class values.
We can thus abstract over them, the same way that we can abstract over any other sort of data.
Instead of (define (f1 x y) (+ x y))
could do (define (f2 op x y) (op x y))
This becomes very useful for abstracting over list processing
The templates we've been writing are common patterns.
The "MAP" pattern
;; g : Listof[Number] -> Listof[Number]
(define g
(lambda (l)
(cond
((null? l) '())
(else (cons (* 2 (car l))
(g (cdr l)))))))
;; h : Listof[Number] -> Listof[Number]
(define h
(lambda (l)
(cond
((null? l) '())
(else (cons (+ 3 (car l))
(h (cdr l)))))))
There is a lot of stuff that is the same between g
and h
above.
Here's another one:
;; strs-to-nums : Listof[String] -> Listof[Number] (define strs-to-nums (lambda (l) (cond ((null? l) '()) (else (cons (string->number (car l)) (strs-to-nums (cdr l)))))))
The only essential difference is that h
has a (+ 3
elem)
where g
has a (* 2
elem)
. That is, they only differ
in what operation they perform on each element elem of the list.
In Scheme, we can abstract over such differences!
;; map : (Number -> Number) * Listof[Number] -> Listof[Number] (define map (lambda (f l) (cond ((null? l) '()) (else (cons (f (car l)) (map f (cdr l))))))) ;; mul2 : Number -> Number (define mul2 (lambda (x) (* 2 x))) ;; add3 : Number -> Number (define add3 (lambda (y) (+ 3 y))) ;; (g lon) is same as (map mul2 lon) ! ;; (h lon) is same as (map add1 lon) !
Exercise: The "FILTER" pattern
;; evens : Listof[Number] -> Listof[Number]
(define evens
(lambda (l)
(cond
((null? l) '())
(else (cond
((even? (car l)) (cons (car l) (evens (cdr l))))
(else (evens (cdr l))))))))
;; all-less-than-ten : Listof[Number] -> Listof[Number]
(define all-less-than-ten
(lambda (l)
(cond
((null? l) '())
(else (cond
((< (car l) 10)
(cons (car l)
(all-less-than-ten (cdr l))))
(else (all-less-than-ten (cdr l))))))))
Ex 8. What are the essential differences between
evens
and all-less-than-ten
above?
Ex 9. Come up with a function, filter
, that is an abstraction of
evens
and all-less-than-ten
For the filter
procedure you define, it should be the case that:
;; (evens l) is same as (filter even? l) ! ;; (all-less-than-ten l) is same as (filter less-than-ten l), ;; where less-than-ten is defined as follows: ;; ;; less-than-ten : Number -> Boolean ;; Produces #t iff n < 10 (define less-than-ten (lambda (n) (< n 10)))Felix S. Klock II