At first glance, self-referential data definitions seem to be far more complex than those for compound or mixed data. But, as the example in the preceding subsection shows, our design recipes still work. Nevertheless, in this section we discuss a new design recipe that works better for self-referential data definitions. As implied by the preceding section, the new recipe generalizes those for compound and mixed data. The new parts concern the process of discovering when a self-referential data definition is needed, deriving a template, and defining the function body:
For a recursive data definition to be valid, it must satisfy two conditions. First, it must contain at least two clauses. Second, at least one of the clauses must not refer back to the definition. It is good practice to identify the self-references explicitly with arrows from the references in the data definition back to its beginning.
Our running example for this section are functions that consume lists of symbols:
In addition, we inspect each selector expression. For each that extracts a value of the same class of data as the input, we draw an arrow back to the function parameter. At the end, we must have as many arrows as we have in the data definition.
Let's return to the running example. The template for a list-processing function contains a cond-expression with two clauses and one arrow:
For simplicity, this book will use a textual alternative to arrows. Instead of drawing an arrow, the templates contain self-applications of the function to the selector expression(s):
(define (fun-for-los a-list-of-symbols)
(cond
[(empty? a-list-of-symbols) ...]
[else ... (first a-list-of-symbols) ...
... (fun-for-los (rest a-list-of-symbols)) ...]))
We refer to these self-applications as NATURAL RECURSIONS.
Then we deal with the self-referential cases. We start by reminding ourselves what each of the expressions in the template line computes. For the recursive application we assume that the function already works as specified in our purpose statement. The rest is then a matter of combining the various values.
Suppose we wish to define the function how-many, which determines how many symbols are on a list of symbols. Assuming we have followed the design recipe, we have the following:
;; how-many : list-of-symbols -> number
;; to determine how many symbols are on a-list-of-symbols
(define (how-many a-list-of-symbols)
(cond
[(empty? a-list-of-symbols) ...]
[else ... (first a-list-of-symbols) ...
... (how-many (rest a-list-of-symbols)) ...]))
The answer for the base case is 0 because the empty list contains nothing. The two expressions in the second clause compute the first item and the number of symbols on the (rest a-list-of-symbols). To compute how many symbols there are on all of a-list-of-symbols, we just need to add 1 to the value of the latter expression:
(define (how-many a-list-of-symbols)
(cond
[(empty? a-list-of-symbols) 0]
[else (+ (how-many (rest a-list-of-symbols)) 1)]))
The figure is not yet translated into HTML.