Advanced Scheme Programming

(for less advanced lessons, see the previous lab.)

Announcement: the reading for next week is Chapter 3 and Appendix B of EoPL.

Parametric Data Definitions

;; 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.

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?
(The answer to this question depends on your answer to the previous exercise; some people, with some correct answer to the previous exercise, will be correct in saying "yes", and others will be correct in saying "no." Can you determine why?)

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.

Following the grammar, encoding constraints in the grammar

;; 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?

Context Parameteters (aka Accumulators)

(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

An easy example of a function where an accumulator is useful is 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:

Now, as for the function itself: let us develop this together using our standard techniques.
(interactive development of a function.)


(interactive development of a new function with a context parameter, also known as an "accumulator.")

;; 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"))

longest-runs

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:

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)) is (a b c b a). (Do not use the 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:

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)) is 102 and (to-ten (list 2 1)) is 21.
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)

Mutual Recursion

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")) 
;; represents andGoogle

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 Numbers).

;; 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) is (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)))

Higher Order Functions

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