Skip to main content

Class 32: Naming Local Procedures

Held:

We explore how and why one writes local recursive procedures.

Preliminaries

Overview

  • Why have local procedures
  • Naming local procedures with letrec
  • Naming local procedures with named let

Updates

News / Etc.

  • Continue partners

Rotating reminders

  • Have a great break!

Upcoming Work

  • No lab writeup!
  • No homework over break (other than the reading).
  • Reading for Monday after break: Turtle Graphics

Extra credit (Academic/Artistic)

Extra credit (Peer)

  • Singers Concerts during spring break.
    • If there is an admission fee, I will reimburse up to $20.
  • Ultimate games during spring break.
    • There should be no admission fee.
  • Baseball games during spring break (Arkansas & Alabama).
    • There should be no admission fee.
  • Baseball games last weekend of spring break. (1 hour suffices; no more than two units for the weekend.)

Extra credit (Misc)

Good things to do

  • Read books.
  • Talk with friends.
  • Enjoy nature.
  • Get sleep.
  • Exercise.
  • Make a difference to someone else.

Pre-break PSA

  • You are awesome people. Take care of yourselves.
  • One can recover from the intensity of half of a Grinnell semester in many ways. You need not ingest substances. You need not engage in carnal acts. If you choose to ingest substances, please do so in moderation. If you do the latter, remember that consent is necessary, and that you should respect your own and your partner’s body.

Local Procedure Bindings

  • Today’s class will focus not on something new, but on a better way to do something old: Define helper procedures.
  • We frequently want to define procedures that are only available to certain other procedures (typically to one or two other procedures).
  • We call such procedures local procedures
  • Most local procedures can be done with let and let*.
  • However, neither let nor let* works for recursive procedures.
  • When you want to define a recursive local procedure, use letrec.
  • When you want to define only one, you can use a variant of let called “named let”.

letrec

  • A letrec expression has the format
(letrec ([*name1* *exp1*]
         [*name2* *exp2*]
         ...
         [*namen* *expn*])
  *body*)
  • A letrec is evaluated using the following series of steps.
    • First, enter *name<sub>1</sub>* through *name<sub>n</sub>* into the binding table. (Note that no corresponding values are entered.)
    • Next, evaluate *exp<sub>1</sub>* through *exp<sub>n</sub>*, giving you results *result<sub>1</sub>* through *result<sub>n</sub>*.
    • Finally, update the binding table (associating *name<sub>i</sub>* and *result<sub>i</sub>* for each reasonable i.
  • Not that its meaning is fairly similar to that of let, except that the order of entry into the binding table is changed.

Named let

  • Named let is somewhat stranger, but is handy for some problems.
  • Named let has the format
(let *name* 
  ((*param1* *exp1*)
   (*param2* *exp2*)
   ...
   (*paramn* *expn*))
  *body*)
  • The meaning is as follows:
    • Create a procedure with formal parameters *param<sub>1</sub>**param<sub>n</sub>* and body *body*.
    • Name that procedure *name*.
    • Call that procedure with actual parameters *exp<sub>1</sub>* through *exp<sub>n</sub>* .
  • Yes, that’s right, we’ve packaged together the procedure definition and the procedure call.
  • In effect, we’re just doing
(letrec ((*name* (lambda (*param1* ...
*paramn
*)
                  *body*)))
   (*name* *val1* ... *valn*))

An Example

  • As an example, let’s consider the problem of writing reverse.
  • A first version, without local procedures
(define reverse
  (lambda (lst)
    (reverse-kernel lst null)))
(define reverse-kernel
  (lambda (remaining so-far)
    (if (null? remaining)
        so-far
        (reverse-kernel (cdr remaining) (cons (car remaining) so-far)))))

The principle of encapsulation suggests that we should make reverse-kernel a local procedure.

(define reverse
  (letrec [(kernel
            (lambda (remaining so-far)
              (if (null? remaining)
                  so-far
                  (kernel (cdr remaining) (cons (car remaining) so-far)))))]
    (lambda (lst)
      (kernel lst null))))

The pattern of “create a kernel and call it” is so common that the named let exists simply as a way to write that more concisely.

(define reverse
  (lambda (lst)
    (let kernel [(remaining lst)
                 (so-far null)]
      (if (null? remaining)
          so-far
          (kernel (cdr remaining) (cons (car remaining) so-far))))))

Lab