Programming Languages and Paradigms

Recall
the function `annotate`
from Session 9. It takes a flat list of symbols, such as
`(ball-state michigan-state northern-iowa)`,
and returns a list of symbol/position lists,
`((ball-state 1) (michigan-state 2) (northern-iowa 2))`.
Solving this problem required a new idea, the interface
procedure.

Then we learned about a new data type, the s-list, which allowed symbols in the list to be nested inside of other lists. S-lists are useful to us because they resemble Racket programs, which means that learning how to process s-lists helps us learn how to process programs of this form.

Can we annotate an s-list? Sure. Instead of a symbol's position in any particular list, we would probably care more about its depth in the tree. For example:

> (annotate-slist '(ball-state michigan-state northern-iowa)) '((ball-state 0) (michigan-state 0) (northern-iowa 0)) > (annotate-slist '((a b) (((b g r) (f r)) c (d e)) b)) '(((a 1) (b 1)) ((((b 3) (g 3) (r 3)) ((f 3) (r 3))) (c 1) ((d 2) (e 2))) (b 0))

Try to write ** annotate-slist** now. This problem
is a straight application of three techniques we have studied:
structural recursion,
mutual recursion,
and
interface procedures.
Then scroll down to see how I solved the problem.

. Spoilers below.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

Let's start with an interface procedure and a data-driven outline of the helper that handles s-lists:

(define annotate-slist (lambda (slst) (annotate-counted slst 0))) (define annotate-counted (lambda (slst level) (if (null? slst) ; handle empty list ; handle cons )))

The empty list case is easy to handle: the result is also the
empty list. The `cons` case is almost as easy: annotate
the `car`, annotate the `cdr`, and return a new
`cons` of the results:

(define annotate-counted (lambda (slst level) (if (null? slst) '() (cons (... annotate) (first...... annotate)))))rest...

The `rest` is an s-list, so a call to
`annotate-counted` will do the trick. The `first`
is a symbol expression, so let's call a helper:

(define annotate-counted (lambda (slst level) (if (null? slst) '() (cons (annotate-sym-expr (first slst) level) (annotate-counted (rest slst) level))))) (define annotate-sym-expr (lambda (sym-expr level) (if (symbol? sym-expr) ; handle symbol ; handle slst)))

We're almost there. Annotating a symbol is easy: we return a
2-list of the symbol and the count. Annotating an s-list isn't
much harder, because we just wrote `annotate-counted`!
All we have to do is record the fact that we are going one
level deeper in the structure. Here goes:

(define annotate-sym-expr (lambda (sym-expr level) (if (symbol? sym-expr) (list sym-expr level) (annotate-counted sym-expr (+ level 1)))))

The result is three procedures that follow from the three design patterns you've used several times over the last few weeks: structural recursion, interface procedure, and mutual recursion. The problem is a challenge but, again, if we proceed systematically, the patterns guide us home.