Naming Local Functions with let

CS 3540
Programming Languages and Paradigms


Local variables can name functions, too.

Here is the code for this reading, in a zip file.

The Problem

We can often make a function simpler by decomposing it into multiple functions. For example, both Mutual Recursion and Syntax Procedure create helper functions that simplify a larger body of code. More importantly, we often cannot write a simple or efficient recursive program without a helper function. For example, Interface Procedure and Accumulator Variables use helper functions as an integral part of making code function in an acceptable way.

The use of helpers, though, can cause some problems:

Program Derivation offers a solution to all three of these problems in many contexts. However, using this technique makes the program denser, and so it may make it more difficult to read and modify the code. If the local function is called in multiple places, it also requires us to duplicate code.

A Solution: Local Functions

A local function offers a solution to the last two of these problems. It makes the function's name local, rather than global, and ensures that the code stays close to the code that use it.

Sometimes, we can create a local function as an ordinary local variable. Consider the procedure invert, which takes as an argument a list of 2-lists (lists of size 2) and returns a list in which each 2-list is reversed. For example:

    > (invert '((kramer 10) (jerry 8) (elaine 7) (george 4)))
    ((10 kramer) (8 jerry) (7 elaine) (4 george))

The simplest way to do this uses map. We might use a standard helper function:

    (define swap
      (lambda (2-list)
        (list (second 2-list) (first 2-list))))

    (define invert
      (lambda (list-of-2lists)
        (map swap list-of-2lists)))

(We could use the Racket primitive reverse in place of swap, but it walks down the list one item at a time, because it takes lists of arbitrary length. That would be less efficient than necessary in our case; we don't need recursion because we know that each list contains exactly two items!)

swap is a common name for utility functions when working with lists of symbols. For example, we sometimes implement a swap function for lists of symbols and s-lists. Introducing a new swap function may clash with an existing function of that name. For instance, we may have already defined a function that looks like this:

    ; existing function in the namespace
    (define swap
       (lambda (first second)
          ;; ... do something ))

or this:

    ; existing function in the namespace
    (define swap
       (lambda (old new s-list)
          ;; ... do something ))

Creating a new swap function for use in invert would clobber those definitions and break other code. If we have not created them already, we may want to in the future, and using the name at the top-level name means we won't be able to.

How can we solve this problem? Well, we could change the name of our new function to something else. This approach raises at least a couple of questions:

There is another way to solve this problem. Because the binding of a function to its name in Racket is just like any other variable binding, we can use a let expression to create a local function:

    (define invert
      (lambda (list-of-2lists)
        (let ((swap (lambda (lst)
                      (list (second lst) (first lst)) )))
          (map swap list-of-2lists)) ))

Remember: functions are first-class values in Racket. Anything we can do with any other value -- say, a number or a list -- we can do with a function. That includes naming one using a local variable.

Because this function definition is more deeply nested, it may seem a bit more complex, at least while you are still learning Racket. Even after you feel more comfortable with this construction, you may not want to write your code in this way in the first place. I often write programs by defining my functions at the global level and then "putting them together" after I know they they work. This is similar to the principle of "never optimize before its time". Code writing time is almost always too early to optimize!

Quick Exercise

We can use program derivation to eliminate the named helper function altogether. What is the resulting program? Which version do you prefer?

Eugene Wallingford ..... ..... March 18, 2021