3.4 Using First-Class, Higher-Order, and Anonymous Procedures (Hunk L)
3.4.1 First-Class Procedures
Scheme procedures are rst-class objects in the language you refer to a procedure in the same way you refer to any other object, via a pointer. A \procedure name" is really just a variable name, and you can do the same things with \procedure" variables as with any other variable. There's really only one kind of variable in Scheme, and it's type is \pointer to anything."
When we \dene a procedure" in Scheme, we're really just dening a variable and giving it an initial value that's (a pointer to) a procedure object.
The procedure dening syntax with parentheses around the procedure name (and argument names) is really just syntactic sugar, i.e., a convenient way of writing something that you could do in another way. do I use \syntactic sugar" earlier? If so, dene earlier. ]
Scheme>(define (double x) (+ x x)) #void is exactly equivalent to Scheme>(define double (lambda (x) (+ x x)) #void
Try this latter version in your system. Notice that what you're doing is just dening a variable named doubleand initializing it with the result of the second expression, a lambda expression.
lambda is the real procedure-creating operation. It's a special form, because it lets you dene
a new procedure rather than calling an existing procedure in the normal way. lambda creates a
procedure object and returns a pointer to it.
(The predicate procedure? can be used to tell if an object is a procedure.)
You can call thedoubleprocedure created this way in exactly the same way as one created with
the sugared procedure-denition syntax.
Scheme>(double 3) 6
Recall how procedure calls really work. When you call a named procedure, e.g., (double 3),
the procedure name is really just a reference to a variable. The rst position in the procedure call form is just an expression that's evaluated like any other. In this case, we're using the namedouble
as an expression, eectively saying \look up the value ofdouble."
Try this
Scheme>double #<procedure>
Notice that we didn't put parentheses around double, so we're not calling it|we're fetching
printed representation of a procedure object. Take a look at it, because you'll want to be able to recognize procedure objects in data structures.
(The printed representation may include the name of the procedure don't be misled by this. Procedures don't really have names|they're just data objects you can have pointers to, as I'll explain shortly. Your system your system may put a name inside the procedure when you use the procedure denition syntax, but it's just an annotation saying what the procedure's original name was|i.e., when it was rst dened.)
We can call a procedure in other ways, though|the rst subexpression of a procedure call can be any expression we want, as long as it returns a procedure. That expression is evaluated just like the argument expressions|after it and the argument expresssions are evalutated, the resulting procedure is called with those argument values.
Scheme>(define list-holding-double (list double)) #void
Scheme>list-holding-double (#<procedure>)
Scheme>((car list-holding-double) 5) 10
What we did here was to create a list holding the procedure formerly known as double, and
looked at that list. Then we called that procedure by using the expression (car list-holding- double) as its \name."
What this shows is that procedures are really anonymous, that is, a procedure doesn't have a name in a direct sense. There are just expressions we can refer to it by, if those expressions result in pointers to the procedure.
We can create procedures without normal names at all, by just using lambda. Let's create another doubling procedure by just evaluating a lambdaexpression:
Scheme>(lambda (x) (+ x x)) #<procedure>
The lambda expression just created a procedure and returned a pointer to it, and Scheme
displayed it however your system does it. We didn't keep a pointer to the procedure, so we can't call it now. The procedure is gone and the garbage collector will clean it up.
We could try again, creating a procedure and keeping a pointer to it in a named variable. More interestingly, we can just hand the pointer to a procedure call, and call it without ever giving it a name.
Scheme>((lambda (x) (+ x x)) 6) 12
It may not look like it, but this is just a procedure call expression, where the \name" of the procedure is a lambda expression to create the procedure we need, and its argument is 6. Note
the nesting of parentheses|this is just like (double 6), except that we give the \denition" of the
procedure to call, instead of its name.
Later we'll show why usinglambdadirectly is often much more convenient than having to name
all of our procedures. I'll also explain whylambdais the most important special form in Scheme|it
is so powerful that most of the special forms can easily be translated into it.
(You might be concerned that creating a procedure and just using it once is very expensive, but it turns out not to be|I'll explain that later, too. For now, don't worry about it.)
3.4.2 Higher-Order Procedures
A higher-order procedure is one that can take procedures as arguments and/or return them as values. We can use that to write generic procedures that do a basic kind of thing, and take arguments that specialize its behavior.
Here's a simple example.
Scheme provides a procedure display, which can write textual representation of a data object
on the screen, much like the way the read-eval-print loop displays results of expressions you type in. (This is a very handy procedure for debugging, as well as for programs that interact with users.)
Suppose, though, that you want to display a list of objects, not just one. You want a routine
list-displayto iterate over a list, and display each item in it. The obvious way to write it is to
Here's a version like that:
Scheme>(define (list-display lis) (if (pair? lis)
(begin (display (car lis))
(list-display (cdr lis)))))
I've written this procedure recursively, because it's easy to use recursion over lists|usually it's easier than using an iteration construct. This procedure checks to see if what it got was a pair, and if so, it displays the rst item, and then calls itself recursively to display the rest of the list. I used abeginto sequence the displaying and the recursive call.
It would be cleaner to use cond, so here's an equivalent version using cond: Scheme>(define (list-display lis)
(cond ((pair? lis)
(display (car lis))
(list-display (cdr lis)))))
(Notice that this is a one-branch conditional, but we use cond instead of if because a cond
branch can be a sequence.) Now try it out:
Scheme>(list-display '(1 2 3)) 123#void
What happened here is that it displayed each item in the list as it was evaluated, and then Scheme printed out the return value.
(We hadn't specied a return value|the procedure stops when it gets to an the end of the list, and doesn't take the rst branch of the one-branch if The return value is whatever your Scheme
system uses as the result of a one branch if when the branch is not taken. It may suppress the
printing of an unspecied value, so you may not see it at all.)
This works, but the procedure is not very general. Iterating over lists is very common, so it would be nice to have a more general procedure that iterates over lists, and applies whatever procedure you want.
We can modify our procedure to do this. Instead of taking just a list argument, it can take an argument that's a procedure, and apply that procedure to each element of the list.
We'll call our procedure list-each, because it iterates over a list and does whatever you want
to each element.
Scheme>(define (list-each proc lis) (if (pair? lis)
(begin (proc (car lis))
(list-each proc (cdr lis)))))
The only change we made was to add an argument proc, to accept (a pointer to) a procedure,
and to change the call todisplay into a call toproc.
Now we can call this general procedure with the argument display, to tell it to display each
thing in the list.
Scheme>(list-each display '(1 2 3)) 123#void
But maybe this isn't what we want. We might want to print each item, and then a newline (go to the next line), to spread things out vertically. We could write a proceduredisplay-with-newline
to do that, but it's easier just to use alambdaexpression to create the procedure we need.
Try this: Scheme>(list-each (lambda (x) (display x) (newline)) '(1 2 3)) 1 2 3 #void
(Scheme has a standard procedure similar to ourlist-each, but more general, calledfor-each.)
================================================================== This is the end of Hunk L
At this point, you should go back to the previous chapter and read Hunk M before returning here and continuing this tutorial. ==================================================================
(Go BACK to read Hunk M, which starts at Section 2.7.4 lambda and Lexical Scope], page 62.)