31B. Using Equations to Derive Algorithms

Defining functions by equations

Let's think about writing a definition of function length(L) that returns the length of list L. For example, length([2, 4, 6]) = 3 and length([ ]) = 0. There are two key rules about lists that you should always keep in mind when defining functions for lists.

  1. There are two kinds of lists: empty lists and nonempty lists. When thinking about lists, think about each kind.

  2. Every nonempty list has a head and a tail. When thinking about a nonempty list, consider its head and tail.

It suffices to say how to compute the length of an empty list and how to compute the length of a nonempty list. But instead of thinking about an algorithm right now, let's focus on facts, and think about those facts using the conceptual view of lists.

(length.1)   length([ ]) = 0  
(length.2)   length(L) = 1 + length(tail(L))   (when L ≠ [ ])

As you can see, each equation can have a proviso indicating requirements for it to hold; the second equation only holds when L is not empty. The second equation tells you, for example, that length([2, 4, 6, 8]) = 1 + length([4, 6, 8]). Since 4 = 1 + 3, that is true.

We have two facts (equations) about the length function. But what we really want is an algorithm to compute the length of a list. Is it reasonable to say that those two equations define an algorithm? Let's try to compute length([2, 4, 6]) using them. The only thing we do is replace expressions with equal expressions, using facts (length.1) and (length.2) and a little arithmetic.

   length([2, 4, 6])
    = 1 + length([4, 6])  by (length.2), since tail([2, 4, 6]) = [4, 6]
    = 1 + (1 + length([6]))  by (length.2), since tail([4, 6]) = [6]
    = 1 + (1 + (1 + length([ ])))  by (length.2), since tail([6]) = [ ]
    = 1 + (1 + (1 + 0))  by (length.1)
    = 3  by arithmetic

Converting to C++

Equations (length.1) and (length.2) are easy to convert into C++. Since there are two equations, there are two cases.

  int length(const ListCell* L)
  {
    if(isEmpty(L)) 
    {
       return 0;                   // by (length.1)
    }
    else 
    {
       return 1 + length(tail(L))  // by (length.2)
    }
  }
It is also okay to use C++ notation directly.
  int length(const ListCell* L)
  {
    if(L == NULL) 
    {
       return 0;
    }
    else 
    {
       return 1 + length(L->tail);
    }
  }
Use whichever form you prefer. But only use C++ notation in C++ function definitions, not in conceptual equations.

Exercises

  1. Using equations (length.1) and (length.2), show an evaluation of length([6, 5, 4, 3]) by only replacing expressions by equal expressions. Answer