In general, a substantial part of the code for a Scheme function
arises from the data structure of the function's inputs.  This note
demonstrates this by example.

Consider a function that operates over a list of numbers.  We can
define a list of numbers as

;; A list-of-nums is either
;;   - empty or
;;   - (cons num list-of-nums)

Assume I've asked you to write a function numlist-func that takes a
list of numbers as input (I haven't told you what this function does,
or what the function outputs).  How much can you write just from
knowing that the input is a list-of-nums?

(Step 1:) For starters, the function must take the list of nums as an
argument and must first determine whether it is empty or a cons (ie,
which of the two cases from the data structure definition applies).
The ... indicate places where we haven't yet finished the function.

(define (numlist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ...]))

(Step 2:) Next, ask yourself which cases have additional information
about the data.  The empty case does not, but the cons case does
(namly, the first and the rest).  We can therefore fill in our program
skeleton further.

(define (numlist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (first alon) ...
                          (rest alon) ... ]))

(Step 3:) Finally, look at the types of the pieces you pulled out.
Are they simple types (number, symbol, boolean, string), or types that
also have structure (lists, define-datatypes, trees, etc)?  If they
have structure, do they refer to the same datatype as the current
function?  If so, you'll need a recursive call on that piece.  If the
piece isn't recursive, you may still want a helper function to process
the complex input.

In the case of numlist-func, the rest of the list is also a
list-of-nums, so we should make a recursive call:

(define (numlist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (first alon) ...
                          (numlist-func (rest alon)) ... ]))

Once you've constructed this skeleton, you just need to fill in the
... based on what the function actually does.  For example, to take
the product of numbers in a list, rename numlist-func to product and
fill in the holes as follows:

(define (product alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (first alon) ...
                          (product (rest alon)) ... ]))


(define (product alon)
  (cond [(empty? alon) 1]
        [(cons? alon) (* (first alon) 
                         (product (rest alon)))]))

This process works for almost all of the programs you will write in
this course.  Let's look some more examples.

------------------------------------------------------------------

Write the program skeleton for a function over vehicles.  Here is the
define-datatype for vehicles:

(define-datatype vehicle vehicle?
  [taxi]
  [bus (seats number?)
       (hand-holds number?)
       (color string?)]
  [subway (cars number?)
          (seats-per-car number?)
          (line symbol?)])

A function over a vehicle must start with a cases construct; this
parallels Steps 1 and 2 from above.

(define (vehicle-func a-vehicle)
  (cases vehicle a-vehicle
    [taxi () ...]
    [bus (seats hand-holds color) ...]
    [subway (cars seats-per-car line) ...]))

For Step 3, we consider the types of the fields on the
define-datatype.  All are atomic types, so there's no need for
recursive calls.

------------------------------------------------------------------

Now let's consider a function over a list of vehicles.

;; A list-of-vehicle is
;;  - empty or
;;  - (cons vehicle list-of-vehicle)

Steps 1 and 2 yield the same skeleton as for list of numbers:

(define (vehiclelist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (first alon) ...
                          (rest alon) ... ]))

What happens in step 3?  We need a recursive call on the rest

(define (vehiclelist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (first alon) ...
                          (vehiclelist-func (rest alon)) ... ]))

but in this case, (first alon) also has structure (it's a vehicle).
So, we can assume we'll call some function on vehicles to process it.
Let's annotate the skeleton with this as follows:

(define (vehiclelist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (vehicle-func (first alon)) ...
                          (vehiclelist-func (rest alon)) ... ]))

So, in this case our skeleton really contains skeletons for two
functions: one for vehicle and one for list-of-vehicle.

Assume we want to write a function contains-blue-bus? that takes a
list-of-vehicle and returns a boolean indicating whether the list
contains any blue buses.  Start with the skeletons:

(define (vehicle-func a-vehicle)
  (cases vehicle a-vehicle
    [taxi () ...]
    [bus (seats hand-holds color) ...]
    [subway (cars seats-per-car line) ...]))

(define (vehiclelist-func alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (vehicle-func (first alon)) ...
                          (vehiclelist-func (rest alon)) ... ]))

Now, rename the two skeleton functions to fit the problem:

(define (blue-bus? a-vehicle)
  (cases vehicle a-vehicle
    [taxi () ...]
    [bus (seats hand-holds color) ...]
    [subway (cars seats-per-car line) ...]))

(define (contains-blue-bus? alon)
  (cond [(empty? alon) ...]
        [(cons? alon) ... (blue-bus? (first alon)) ...
                          (contains-blue-bus? (rest alon)) ... ]))

Finally, fill in the holes to implement the two functions.  Notice how
little code is required over the template.

(define (blue-bus? a-vehicle)
  (cases vehicle a-vehicle
    [taxi () false]
    [bus (seats hand-holds color) (string=? color "blue")]
    [subway (cars seats-per-car line) false]))

(define (contains-blue-bus? alon)
  (cond [(empty? alon) false]
        [(cons? alon) (or (blue-bus? (first alon)) 
                          (contains-blue-bus? (rest alon)))]))

------------------------------------------------------------------

One last example: let's define binary trees of numbers:

(define-datatype numtree numtree?
  [bottom]
  [node (num number?)
        (left numtree?)
        (right numtree?)])

Here's the full program skeleton.  Do you see where each piece came
from?

(define (numtree-func atree)
  (cases numtree atree
    [bottom () ...]
    [node (num left right) ... (numtree-func left) ...
                               (numtree-func right) ... ]))

Now, we can fill in the skeleton to write programs such as sum-tree:

;; sumtree : numtree -> number
;; sum all numbers in the tree
(define (sumtree atree)
  (cases numtree atree
    [bottom () 0]
    [node (num left right) (+ (sumtree left) 
			      (sumtree right))]))

Or, we can write in-tree? to check whether a number is in the tree

;; in-tree? number numtree -> boolean
;; determine whether given number is in the tree
(define (in-tree? anum atree)
  (cases numtree atree
    [bottom () false]
    [node (num left right) (or (equal? anum num)
                               (in-tree? anum left) 
                               (in-tree? anum right))]))

------------------------------------------------------------------

SUMMARY

Program skeletons are an extremely useful tool, because they capture
the code that traverses a data structure.  If you start with the full
program skeleton, you never need to think about the traversal; you
only need to think about what you do at each point in the data
structure.  They also help you develop a lot of the code upfront,
which lets you focus in on the interesting parts of the code.

In general, your code should always follow the structure of the
skeletons so that you get the traversals right.  If you are faced with
a new program and don't know where to start, write the skeleton first!
If you can't write the skeleton, you almost certainly won't be able to
implement the function either.

Once you have the skeleton, if you're still struggling with a program,
write down examples of the program's inputs and outputs.  These
examples will help determine whether you are stuck on what the
function does, or how it should do it.  If you can't write the
examples, then you don't know what the function should do.  If you can
write the examples but not fill in the skeleton, then you are stuck on
how to implement it.