CS 1321X - Lecture 20 - October 28, 2003

CS 1321X - Lecture 20

Procedures Without Names


I.  Another way to get repetitive operations

Bryan Kennedy presented this topic in class for me, and 
he posted his notes on one of our newsgroups.  Here's my
brief (and not nearly as comprehensive) overview of the 
material:

Recursion is just a nice way to get iterative ability out of 
our computer programs without adding the complexity that 
comes with the introduction of counter variables and 
accumulator variables and assignment operations.  How do we 
know that things get more complex when we add that stuff?  
Well, for starters, think about that nice simple substitution 
model of evaluation---it's pretty understandable, no?  
Imagine what that looks like if we add variables and 
assignment.  If you could keep track of it all, every 
substitution involves keeping track of all the variables and 
what they're bound to at every given step of the way...ugh.  
It's no longer simple.  In fact, the substitution model 
becomes so cumbersome under these conditions that we have to 
come up with other models of evaluation just to accommodate 
the complexity.  But I digress.  

There's yet another way to get repetition out of our 
procedures, and that falls under the header of "applicative 
programming" as opposed to "recursive programming".  To 
demonstrate how applicative programming works, though, we'll 
start with a recursive explanation of applicative 
programming.

When using a programming language whose basic data structure 
is the list, it's not surprising that one of the things we 
want to do most often is perform the same procedure on all 
the elements of a list.  We already know how to do that 
recursively.  Say for example, that we want to take a list of 
numbers as input and return a list of corresponding square 
roots.  That procedure might look like this (do you recognize 
the recursion template?):

(define (lotsa-sqrts input-list)
  (cond [(null? input-list) ()]
        [else (cons (sqrt (car input-list))
                    (lotsa-sqrts (cdr input-list)))]))

and it would work like this:

> (lotsa-sqrts '(1 4 9 16 25))
(1 2 3 4 5)
> 

Now, since we often want to do repetitive operations like 
this, and most of the time that operation will probably be 
something other than the square root procedure, wouldn't it 
be nice if we could construct a generic function to which we 
could pass another function and a list of things to do be 
worked on in succession by that function?  Well of course it 
would!  And folks, note here that when I say we want to pass 
it a function, I don't mean passing it a function call to be 
evaluated, as in (sqrt (* 2 2))...that's different.  I mean 
we want to pass the whole function definition as a parameter.  
Eek!  How the %$#@! are we gonna do that?!


II.  The case of the headless function

In order to make this happen, there are some things you need 
to know first.  Remember way back when we were touting all 
the things that made Scheme so nice?  One of those features was 
the lack of any procedure/data distinction.  Here's one place 
where that feature comes in handy.

Since a procedure can be treated as a piece of data, we can 
decompose procedures into component parts.  That means we can 
separate a function body from its name.  (How did that body 
get that name?  The "define" function did it, remember?)  In 
other words, it's possible (and in many cases desirable, as 
we're about to see) to use a nameless, anonymous function 
definition in our computations.  Scheme of course has a 
predefined function for separating function bodies from their 
names, and it is named, ironically, "function".  Here's how 
it works:

Let's say you've defined a new function of your own, which in 
this case takes a number as an argument and returns its 
square.  When you use "define" to create that function:

(define (sqr number)
  (* number number))

you've told Scheme to bind the name "sqr" to everything that 
follows the name "sqr" in your definition.  That association 
is then stored in the symbol table.  Later, when you call the 
"sqr" function, Scheme looks up "sqr" in the symbol table and 
retrieves the function body associated with it, then uses 
that function body to figure out what to do.  If we want to 
tell Scheme at some point to explicitly go to the symbol table, 
look up the "sqr" function, retrieve the function body and 
return it without doing anything else with it, then we can 
just say:

> sqr

And in Dr. Scheme, we'll see this:

#
> 

That's Dr. Scheme's way of saying "here's the procedure 
that's associated with the name 'sqr'...you can't 
recognize the function body, because it's compiled, but
here it is in case you want to use it in your computation."
If you could see the details somehow, it would be some
representation of the function body without the name,
which would look something like this:

(lambda (number)
  (* number number))

It's a function body without its name; it's called a 
lambda function.


III.  What can you do with a lambda function?

Back to the original problem: how do we create a generic 
function like "lotsa-sqrts" that isn't restricted to just 
computing square roots?  How do we get it to use any function 
we pass it as an argument?  How do we turn "lotsa-sqrts" into 
"lotsa-anything"?

Let's borrow the "lotsa-sqrts" and make a few necessary 
changes and see where that gets us:

(define (lotsa-anything function input-list)
  (cond [(null? input-list) ()]
        [else (cons (function (car input-list))
                    (lotsa-anything function (cdr input-list)))]))

We know we needed to pass a function body as an argument, so 
we added that argument.  Everything else is pretty much the 
same, except we plugged in the parameter name "function" in 
place of "sqrt".  Will this work?

> (lotsa-anything sqrt '(1 4 9 16 25))
(1 2 3 4 5)
> 

Of course it does!  We pass the name of the function we want
to apply to all the elements of input-list to "lotsa-anything".
Scheme looks for the function body associated with that name
and does what we want.  Most languages can't do this.

The net result is a function which maps another function onto 
the elements of a list, performs that operation on each of 
those elements, and returns a list of the results.

Because "lotsa-anything" sort of scoots along, mapping the 
passed function body onto succeeding "car"s of the ever-shrinking 
"input-list", this function resembles a function called "map".  
The "map" function is already predefined in Scheme, and works 
sort of like what we've defined here:  it applies the given function 
to successive "car"s of the list, collects the individual results 
into a list, and returns that list.  Despite the similarities, "map" 
isn't the same as "lotsa-anything"---"map" is much more powerful.
So while building the "lotsa-anything" function gives us an idea 
as to what the "map" function does, you should take the time to 
look up "map" in the documentation that comes with Dr. Scheme and 
become familiar with what's really going on there.

Here's how I can substitute the elegant, pre-defined "map" function
for my crude "lotsa-anything" slop and get the same result:

> (map sqrt '(1 4 9 16))
(1 2 3 4)

One thing that "map" can do that "lotsa-anything" can't is
take functions that expect more than one argument.  If, for
example, I wanted to "cons" a bunch of elements of one list
into their respective elements of another list, I'd just do
this:

> (map cons '(a b c) '(1 2 3))
((a . 1) (b . 2) (c . 3))
> 

(Dotted pairs...yuk!  Anyway, we move on...)

Oh, I almost forgot to mention that whereever I'm passing the 
name of a function, I could just pass a lambda body without a
name and things will work just fine:

> (map (lambda (number) (* number number)) '(1 2 3 4 5))
(1 4 9 16 25)
>

So "map" provides us with another means of getting repetitive 
operations without the traditional looping structure.  
Again, this style of programming is called "applicative 
programming" to distinguish it from "recursion" or 
"iteration".



Copyright (c) 2003 by Kurt Eiselt.  All rights reserved, with 
the exception of stuff that belongs to somebody else.

Last revised: December 8, 2003