Programming Inside-Out

Many students struggle with Scheme programming because they program
outside-in rather than inside-out.  If you get to as far as (define
(func-name args) ...) and then get stuck figuring out what the next
character should be (other than the open paren, of course), you may be
having this problem.

The easiest way to explain the difference is through an example.  Say
I asked you to write a program that would extract 'pizza from the list

   (cons 'milk
         (cons 'apple
               (cons 'pizza empty)))

To start this program, ask yourself what operators you can use on a
cons: first and rest.  Which do you need here?  'pizza is clearly in
the rest, so we write:

   (rest (cons 'milk
               (cons 'apple
                     (cons 'pizza empty))))

Evaluating this leaves us with 

   (cons 'apple
         (cons 'pizza empty))

Where is 'pizza now?  Still in rest, so we wrap another rest around
the outside:

   (rest (rest (cons 'milk
                     (cons 'apple
                           (cons 'pizza empty)))))

Evaluating this leaves us with (cons 'pizza empty).  From this list,
we can get to 'pizza by using first:

   (first (rest (rest (cons 'milk
                            (cons 'apple
                                  (cons 'pizza empty))))))

This program extracts 'pizza, as we'd hoped.  We wrote it inside-out,
starting from the data, pulling out the pieces we needed, and wrapping
more operators around the outside to build up to the final answer.
Outside in programming, in constrast, would expect you to write the
program in order from left-to-right.  But if you tried programming
that way, you'd get stuck (until you started thinking inside-out and
writing outside-in, which doesn't scale).

You're actually used to programming by thinking inside out from other
languages, but you write the program down in order.  For example, in
other languages you might try to write this program as (something
like)

  list1 := (cons 'milk
                 (cons 'apple
                       (cons 'pizza empty)))
  list2 := (rest list1)
  list3 := (rest list2)
  list4 := (first list3)
  return list4 

Note that the reasoning process is exactly the same as we used in
Scheme, but the order in which you write that reasoning down has
changed.  Don't try to write Scheme programs down in order.  Leave
space, use the text editor, but write programs down from the
inside-out.  Once you change how you expect the program to appear on
the page, Scheme programming becomes a lot easier.

We can see this same style of programming in the sells-gas? program
from earlier this week:

   ;; sells-gas? : tiger -> boolean
   ;; determine whether tiger sells gas 
   (define (sells-gas? atiger)
      ...)

What now?  Work inside-out.  Start with what you know.  You have a
tiger.  What could you do with a tiger?  Well, you can extract its
name, length, and sells information.  So we might write down:

   ;; sells-gas? : tiger -> boolean
   ;; determine whether tiger sells gas 
   (define (sells-gas? atiger)
      (tiger-name atiger) ...
      (tiger-length atiger) ...
      (tiger-sells atiger) ...)

[the ... mean that I haven't finished the program yet]

Now that we know what we can do from a tiger, which piece do we need?
To find out what a tiger sells, we need the sells info, but not the
name or the length.  So erase those two.

   ;; sells-gas? : tiger -> boolean
   ;; determine whether tiger sells gas 
   (define (sells-gas? atiger)
      (tiger-sells atiger) ...)

What is tiger-sells?  If you look at the data definition, it's a
product.  What can you do with products?  You can extract items and
companies.  Which do we need here?  The item.  So we write:

   ;; sells-gas? : tiger -> boolean
   ;; determine whether tiger sells gas 
   (define (sells-gas? atiger)
      (product-item (tiger-sells atiger)) ...)

[again, writing it on the outside, since we're programming
inside-out]. 

What do we have now?  A symbol.  What do we need to know about that
symbol?  Whether is is 'gas.  So we add a symbol=? to do the comparison:

   ;; sells-gas? : tiger -> boolean
   ;; determine whether tiger sells gas 
   (define (sells-gas? atiger)
      (symbol=? 'gas (product-item (tiger-sells atiger))))

There's the finished program.  Again, we built it up from the inside
working out to a full program.  It's the same thinking process you
know from before, you just write the program down slighly
differently.