[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: functional programming is great, but why lists?



   There's a good reason why lists don't naturally come with indices
   the way vectors do: they're not intended to be accessed at random.
   They really are intended to be accessed sequentially.

Thanks, Shriram.  I think I finally understand (after your generative
Recursion remark) that my vector stuff has been nonsense:

Lists are great for the purpose of teaching a simple kind of
recursion, what HTDP calls structural recursion.  I'd be happy to
teach a course on structural recursion, without bringing in vectors.

Here's what I understand now, I don't have it all figured out:

Let's try to write a recursive functional procedure to implement a
function F defined on Lists.

As HTDP says in `Structural versus Generative Recursion', this often
"requires a deep, often mathematical, insight into the problem-solving
process itself".  So it's great to start students off in the simpler
world of structural recursion, which includes functions F satisfying
recursion rules like either:

( F L ) = (G (F (cdr L)))	       for L nonempty

or

( F L ) = (G (car L) (F (cdr L)))	for L nonempty

for some function G we have to solve for.

The first type of function is pretty simple, like length:

(define (length L)
  (if (null? L)
      0
      (add1 (length (cdr L)))))

All the recursive TSS type procedures I understand fit the 2nd form.
No doubt more complicated recursion rules would still be called
structural recursion, and I need to learn about that.  Here's 2
examples:

Let's do forward cyclic permutations, which I falsely posted was such
a challenge yesterday.  It's easy this structural recursion way:

So my function F cycles a list forward, like this:

(F (x_1 ... x_n)) =  (x_n x_1 ... x_{n-1})

Let's look for a rule like:

( F L ) = (G (car L) (F (cdr L)))	for L nonempty

which amounts to solving for G in the equation

(x_n x_1 ... x_{n-1}) = (G x_1 (x_n x_2 ... x_{n-1}))

But that's obvious!  G basically transposes the 1st 2 elements, and
it's easier to describe in Scheme:

(define (forward-cyclic-permutation L)
  (letrec ((G (lambda (x M)
                (if (null? M)
                    (cons x '())
                    (cons (car M) (cons x (cdr M)))))))
    (if (null? L)
        '()
        (G (car L) (forward-cyclic-permutation (cdr L))))))

(forward-cyclic-permutation '(a b c d e f))
(forward-cyclic-permutation '(1 2 3 4 5 6 7 8 9 10))

and we see it works:

(f a b c d e)
(10 1 2 3 4 5 6 7 8 9)

The moral of this isn't that I know everything about structural
recursion.  The moral is that by drastically restricting the kinds of
solutions we're looking for, we might improve our chances, and it's a
good place to start!  That's a powerful pedagogical principle, used
all the time in math classes.

Here's an example of how I've got more to learn.   You wrote:

     If you use the design recipes in How to Design Programs, there's
     nothing gem-like about writing REVERSE -- it's easy, simple and
     boring, just as it should be.

Here's what's boring to me, i.e. I understand it.   Let's call R the
reverse function, which does

(R (x_1 x_2 ... x_n)) =  (x_n ... x_2 x_1)

I want a recursion rule 

( R L ) = (G (car L) (R (cdr L)))	for L nonempty, meaning 

(x_n ... x_2 x_1) = (G x_1 (x_n ... x_3 x_2))

The solution stares at us!  Even I can see that (G x M) appends x onto
the end of a list M.  OK, so we've passed our test, but now we want a
recursive procedure for G.   Let's fiddle with the car & cdr of M:

(G x (m_1 ... m_n)) = (m_1 ... m_n x) = (cons m_1 (m_2 ... m_n x))

and that clearly satisfies the equation

(G x M)  = (cons (car M) (G x (cdr M)))

and I've got a klunky reverse:

(define (reverse L)
  (letrec ((append->end-of-list
            (lambda (x M) 
              (if (null? M)
                  (cons x '())
                  (cons (car M) 
                        (append->end-of-list x (cdr M)))))))
    (if (null? L)
        '()
        (append->end-of-list (car L) 
                             (reverse (cdr L))))))

(reverse '(a b c d e f))
(reverse '(1 2 3 4 5 6 7 8 9 10))

=> 
(f e d c b a)
(10 9 8 7 6 5 4 3 2 1)

My function wrote itself, but it's a kludge compared to the more
elegant TLS reverse:

(define (reverse L)
  (letrec ((reverse-help
            (lambda (L M) 
              (if (null? L)
                  M
                  (reverse-help (cdr L)
                                (cons (car L) M))))))
    (if (null? L)
        '()
        (reverse-help L '()))))

That doesn't bore me yet, so I need to book up on "the design recipes
in How to Design Programs".   Actually, this reverse bores me in one
respect, it looks kinda like an iteration

M = '();
i = n;
while (i > 0) {
  M = (cons x_i M);
  i--;
}

you see I've got more to learn.  Sorry for posting so often, but I
feel like I really learned an important pedagogical point, the value
of using inflexible lists as a way to present structural recursion.  

A final point: I'll never believe HTDP really has "design recipes"
until I see the equations, recursion rules like
( F L ) = (G (car L) (F (cdr L)))
You guys have too much programming/mathematical ability for me to
really believe you when you say things are boring or cookbook recipes.