Introduction to Scheme

Review

Problem Solving

Modules

In PLT Scheme, one can use modules as a way to subdivide your program and control which variables are accessible in the different parts. Within a module, you use the require form to pull in bindings from other modules, and the provide form to export your definitions to the outside world.
(module name lang
  (require module)
  (provide name)
  definitions ...
  expressions ...
  )

In this class, we will tend to


;; Save this in a file, "bootcamp1.scm"
(module bootcamp1 (lib "eopl.ss" "eopl")
  (provide hi-there)
  (define hi-there "Hi There!")
  (define i-am-hidden "You normally can't see me.")
  )

;; Save this in a file, "bootcamp2.scm"
(module bootcamp2 (lib "eopl.ss" "eopl")
  (require "bootcamp1.scm")

  (display hi-there)
  (newline)

  ;;; Does the below work when uncommented?
  ; (display i-am-hidden)
  )
Try hitting the "Run" button while looking at "bootcamp1.scm", and typing the following expressions at the read-eval-print loop:
> hi-there
...
> i-am-hidden
...
What about doing the same, but with "bootcamp2.scm"?

Defining Lists

;; A LoN (list of numbers) is one of:
;; - '()
;; - (cons Number LoN)

Example lists:

;; the empty list []
(define list-1  
  '())

;; the three element list [1, 2, 3]
(define list-2
  (cons 1 (cons 2 (cons 3 '()))))

;; the two element list ["hello", "world"]
(define list-3
  (cons "hello" (cons "world" '())))

Is paired a LoN? Why or why not?

Is list-2 a LoN? Why or why not?

Is list-3 a LoN? Why or why not?

The list procedure provides a useful shorthand for the common pattern (cons A (cons B ... (cons Z '()))) You use it like so: (list A B ... Z)

;; the empty list []
(define list-4
  (list))

;; the three element list [1, 2, 3]
(define list-5
  (list 1 2 3))

;; the two element list ["hello", "world"]
(define list-6 
  (list "hello" "world"))

Processing Lists

BEWARE: you almost never want to say list in the definition of a function; if you are tempted to write list, first ask yourself, "wait, do I mean cons here?"

;; lon-add3 : LoN -> LoN
;; Produces a new list where each number in l has been incremented by 3
;; examples: (lon-add3 (list 5 7 10)) is (list 8 10 13)
;;           (lon-add3 '())           is '()
(define lon-add3 
  (lambda (l)
    (cond
     ((null? l) ...)
     ((pair? l) ... (car l) ...
      ... (lon-add3 (cdr l)) ...))))

Note that for this class of data, else is just as good as pair? in the last clause. (Why is this true?)

But that might not be true in general; double-check your case analysis when you start working with a new class of data.

;; lon-sum : LoN -> Number
;; Produces the sum of all the numbers in l
;; examples: (lon-sum (list 5 7 10)) is 22
;;           (lon-sum '())           is 0
(define lon-sum 
  (lambda (l)
    (cond 
     ((null? l) ...)
     (else      ... (car l) ...
      ...  (lon-sum (cdr l)) ...))))
;; A LoS (list of strings) is one of:
;; - '()
;; - (cons String LoS)

;; append-the-strings : LoS -> String
;; Produces the concatenation of all the strings in l
;; examples: (append-the-strings (list "hi" "thar")) is "hithar"
;;           (append-the-strings '())                is ""
(define append-the-strings 
  (lambda (l)
    (cond
     ((null? l) ...)
     ((pair? l)           ... (car l)  ...
      ... (append-the-strings (cdr l)) ...))))

A common way to approach solving these problems: pretend that the function "magically works", at least for the recursive call. Based on that assumption, figure out how to combine the result of the recursive call with the other values you have in your hands (usually the car and maybe some extra data) to get the desired answer. It is just as "magical" as mathematical induction!

Extra parameters

Consider the following specification and template:

;; how-many-of : Number * LoN -> Number
;; Produces the number of times i occurs in the list l
;; examples: (how-many-of 3 (list 1 2 3 2 4 3)) is 2
;;           (how-many-of 4 '())                is 0
(define how-many-of 
  (lambda (i l) 
    (cond 
     ((null? l) ...)
     (else         ... (car l) ...
      ... (how-many-of (cdr l)) ...))))

What are some problems with the above template? Do these code fragments make sense?

Exercises:

(Remember: when you're about to write a function, start off by writing a few tests for the function! Then see if you can develop the "template" or "skeleton" for the function, based solely on the data definition for one of its inputs.)

Ex 1. Implement any-greater-than-five, which has the following contract and purpose:

    ;; any-greater-than-five : LoN -> Boolean
    ;; Produces #t iff there exists i in l such that i > 5
    ;; examples: (any-greater-than-five (list 1 5 3)) is #f
    ;;           (any-greater-than-five (list 1 6 2)) is #t
    ;;           (any-greater-than-five '())          is #f
    (define any-greater-than-five 
      (lambda (l) 
        ...))

Ex 2. Implement first-negative, which has the following contract and purpose:

    ;; first-negative : LoN -> Number or #f
    ;; Produces the first negative number in l, 
    ;; or #f if there are no negative numbers in l
    ;; examples: (first-negative (list 1 -2 -3 5)) is -2
    ;;           (first-negative (list 10 20 30))  is #f
    ;;           (first-negative '())              is #f
    (define first-negative 
      (lambda (l)
        ...))

Ex 3. Implement first-less-than, which has the following contract and purpose:

    ;; first-less-than : Number * LoN -> Number or #f
    ;; Produces the first number j in l such that j < i.
    ;; (Produces #f if no such j exists in l)
    (define first-less-than 
      (lambda (i l) 
        ...))

Ex 4. Implement first-longer-than, which has the following contract and purpose:

    ;; first-longer-than : Number * LoS -> String or #f
    ;; Produces the first string s in l such that the length of s 
    ;; is greater than i; produces #f if no such s exists.
    (define first-longer-than 
      (lambda (i l)
        ...))

Ex 5. Implement all-greater-than, which has the following contract and purpose:

    ;; all-greater-than : Number * LoN -> LoN
    ;; Produces a list with all and only the elements from l that 
    ;; are greater than i
    ;; examples: (all-greater-than 5 (list 1 6 2 8 3)) is (list 6 8)
    (define all-greater-than 
      (lambda (i l)
        ...))

Ex 6. Here's an example of why just guessing the code to write is not going to work:

    ;; lon-append : LoN * LoN -> LoN
    ;; Produces a list that has all of the elements of l-1
    ;; first, followed by all the elements of l-2.
    ;; examples: (lon-append (list 5 7 8) (list 4 9)) is (list 5 7 8 4 9)
    ;;           (lon-append '() '())                   is '()
    (define lon-append 
      (lambda (l-1 l-2)
        (cond 
         ((null? l-2) l-1)
         ((pair? l-1) (cons (car l-2)
                            (lon-append (cdr l-1) (cdr l-2)))))))
What problems do you see with the above implementation of ``lon-append``? What inputs can you come up with that cause it to fail, and in what ways? Implement `lon-append` (Start from scratch rather than trying to adapt the broken code above.)

Lists of lists, Lists of pairs

A LoLoN is defined as follows:

    ;; A LoLoN (list of lists of numbers)
    ;; - '()
    ;; - (cons LoN LoLoN)

Exercises:

Ex 7. Give three distinct examples of LoLoNs.

Ex 8. Implement sum-the-lons (first fill in examples, then write the code):

    ;; sum-the-lons : LoLoN -> Number
    ;; Produces the sum of summing all the lists in lol
    ;; examples: ???
    (define sum-the-lons 
      (lambda (lol)
        ...))

(Rule of thumb: make a separate function to process each kind of data, rather than trying to shoehorn all of the code into one function. In this particular case, don't forget about lon-sum above!)

Ex 9. Implement largest-of

    ;; largest-of : LoLoN * Number -> Number
    ;; Produces the largest number in lol greater than j.
    ;; If no such number exists, produces j.
    (define largest-of 
      (lambda (lol j)
        ...))

(Don't forget the rule of thumb from Ex 8!)

A LoPT is defined as follows:

    ;; A LoPT (list of paired things) is one of:
    ;; - '()
    ;; - (cons (cons Number String) LoPT)

Ex 10. Give three examples of LoPTs.

Ex 11. Make up examples for lopt-lookup and implement it:

    ;; lopt-lookup : LoPT * Number -> String or #f
    ;; Produces the first string paired with a-num in a-lopt, or #f if
    ;; a-num does not occur in a-lopt.
    (define lopt-lookup 
      (lambda (a-lopt a-num)
        ...))

Hint: here is a template for processing a LoPT

(define lopt-lookup 
  (lambda (a-lopt)          
     (cond
       ((null? a-lopt) ...)
       (else    ... (car (car a-lopt)) ...
                ... (cdr (car a-lopt)) ...
        ... (lopt-lookup (cdr a-lopt)) ...))))

Ex 12. Implement plot-loopkup, where a PLoT is as follows:

    ;; A PLoT (pairwise list of things) is one of:
    ;; - '()
    ;; - (cons Number (cons String PLoT))

    ;; plot-lookup : PLoT * Number -> String or #f
    ;; Produces the string that follows the first occurrence of a-num 
    ;; in a-lopt, or #f if a-num does not occur in a-lopt.

Other Classes of Recursive Data

Trees

    ;; A ToN (tree of numbers) is one of:
    ;; - Symbol
    ;; - (cons Number (cons ToN ToN))

    ;; Example Trees:
    (define tree-1 'a) ;; a leaf
    (define tree-2 'b) ;; another leaf
    (define tree-3 (cons 3 (cons 'a 'b))) ;; an interior node with two leaves

    (define tree-4 (cons 15 (cons (cons 3 (cons 'a 'b)) 'c)))

    (define tree-5 (cons 20 (cons (cons 13 (cons 'x 
                                                 'y))
                                  (cons 7 (cons 'm 
                                                'n)))))

Trees are an interesting class of data.
Lets consider writing a function to process them.

    ;; ton-weight : ToN -> Number
    ;; sums all the interior nodes of a-ton
    (define ton-weight 
      (lambda (a-ton)
        (cond
         ((null? a-ton) ...)
         (else ... (car a-ton) ...
           ... (ton-weight (cdr a-ton)) ...))))

What's wrong with this template?

What should the template look like?

As a class, lets implement ton-weight (don't bother trying to adapt the code above).

Ex 13. Implement ton-contains?

    ;; ton-contains? : ToN * Symbol -> Boolean
    ;; produces #t if a-ton contains s as a leaf somewhere.
    ;; otherwise produces #f.
    (define ton-contains? 
      (lambda (a-ton s)
        ...))

The define-datatype form

It is really painful to try to make complex structures when all you have is car and cdr.

After all, (car (cdr (car (car ...)))) is a bit ugly.

Scheme programmers work around this in various ways. We will use a new form, the define-datatype form, as a tool for making (non-list) structured data.

(define-datatype type-name predicate-name
  (variant1-name (field-name field-predicate)
                 ...)
  (variant2-name (field-name field-predicate)
                 ...)
  ...)

Lets look at ToN again.


(define-datatype dton-type dton?

  (make-leaf (sym symbol?))

  (make-node (num number?) 
             (left dton?) 
             (right dton?))
  )

;; A DTon is one of:
;; - (make-leaf Symbol)
;; - (make-node Number DTon DTon)

;; Below are some examples of DTons.

(define dtree-1 (make-leaf 'a))
(define dtree-2 (make-leaf 'b))
(define dtree-3 (make-node 3 (make-leaf 'a) (make-leaf 'b)))
(define dtree-4 (make-node 15 
                          (make-node 3
                                     (make-leaf 'a) 
                                     (make-leaf 'b))
                          (make-leaf 'c)))
(define dtree-5 (make-node 20 
                           (make-node 13 (make-leaf 'x) (make-leaf 'y))
                           (make-node 7  (make-leaf 'm) (make-leaf 'n))))

The definitions for dtree-# above are examples of how to use the constructors that the define-datatype form introduces.

Use the interactions window in DrScheme to see what dtree-5 looks like. What happens if you try to do (make-leaf dtree-4)?

What about pulling out the sub-components of one of these structures? (That is, what do we have that is analogous to car and cdr, but for these new structures rather than pairs?)

To select the subcomponents of a structure, we use the cases special form.

(cases type-name expression-producing-element-of-type
  (variant1-name (names-for-variant1-fields ...) result1-expression)
  (variant2-name (names-for-variant2-fields ...) result2-expression)
  ... )

So at the interactions window, we can directly investigate how to use the cases special form with the trees we made above.

What do the following expressions do?


> (cases dton-type dtree-1 (make-leaf (x) 'leaf) (make-node (n l r) 'node))
...
> (cases dton-type dtree-2 (make-leaf (x) 'leaf) (make-node (n l r) 'node))
..
> (cases dton-type dtree-3 (make-leaf (x) 'leaf) (make-node (n l r) 'node))
...
> (cases dton-type dtree-4 (make-leaf (x) 'leaf) (make-node (n l r) 'node))
...
> (cases dton-type dtree-5 (make-leaf (x) 'leaf) (make-node (n l r) 'node))
...

And how about these?


> (cases dton-type dtree-1 (make-leaf (x) x) (make-node (n l r) n))
...
> (cases dton-type dtree-2 (make-leaf (x) x) (make-node (n l r) n))
..
> (cases dton-type dtree-3 (make-leaf (x) x) (make-node (n l r) n))
...
> (cases dton-type dtree-4 (make-leaf (x) x) (make-node (n l r) n))
...
> (cases dton-type dtree-5 (make-leaf (x) x) (make-node (n l r) n))
...

What goes wrong with these expressions? Can you explain what is inherently wrong with these expressions, in terms of the data definition for DTon?


> (cases dton-type dtree-1 (make-leaf (x) x) (make-node (n l r) (symbol->string n)))
...
> (cases dton-type dtree-2 (make-leaf (x) x) (make-node (n l r) (symbol->string n)))
..
> (cases dton-type dtree-3 (make-leaf (x) x) (make-node (n l r) (symbol->string n)))
...
> (cases dton-type dtree-4 (make-leaf (x) x) (make-node (n l r) (symbol->string n)))
...
> (cases dton-type dtree-5 (make-leaf (x) x) (make-node (n l r) (symbol->string n)))
...

Here is the template for processing one of these DTon's. Note that you use the same process to develop this template that you use to develop any other: by asking

Its just looks a little different than the way we do it with cond, because the cases form is designed to work with the define-datatype form.


;; f : DTon -> ???
(define f
  (lambda (dtree)
    (cases dton-type dtree
      (make-leaf (x) ... x ...)
      (make-node (n l r) ... n ... (f l) ... (f r) ...))))

Ex 14. Implement a function analogous to ton-weight that operate on DTons instead of ToNs.

Ex 15. Implement a function analogous to ton-contains? that operate on DTons instead of ToNs.