Advanced Student |
No matter how often we use a function with one and the same argument, we always get the same result. Even an accumulator-style function produces the same result every time we apply it to the same argument, as long as the accumulator argument is also the same. Functions simply do not have any memory about their past uses.
Many programs, though, must remember something about their past uses. Recall that a program typically consists of several functions. In the past we have always assumed that there is one main function and that all others are auxiliary and invisible to the user. In some cases, however, a user may expect more than one service from a program, and each service is best implemented as a function. When a program provides more than one function as a service to the user, it is common that, for sheer convenince or possibly because we add a graphical user interface, the functions must have memory.
Because this point is difficult to grasp in the abstract, we study some examples. The first one concerns a program for managing telephone numbers in an address book. The standard address book software provides at least two services:
a service for looking up the phone number of some person; and
a service for adding a name and a phone number to the address book.
Based on our guidelines, the program provides two functions to the user. The user can apply those functions in DrScheme's Interactions window to appropriate data. Or, we can develop a graphical user interface with text fields and buttons so that the user doesn't need to know anything about programming. Figure 95 displays such an interface.
|
The two services roughly correspond to two functions:
;;lookup : list-of-symbol-number-pairs symbol -> number or false
;; to lookup the number associated withname
inpb
;; if it doesn't findname
, the function producesfalse
(define (lookup pb name) ...) ;;add-to-address-book : symbol number -> void
;; to addname
andnumber
toaddress-book
(define (add-to-address-book name number) ...) (define ADDRESS-BOOK (list (list 'Adam 1) (list 'Eve 2)))
We also introduce a variable definition for maintaining a list of name-number associations.
The first function is a variant of our very first recursive function. A
user applies it to a list of name-number associations, such as
ADDRESS-BOOK
, and a name. It produces a number, if the name is on
the list, or false
otherwise. The second function is radically
different from what we have seen. The user would apply it to a name and a
number; any future lookup of that name would then produce that number.
Let's imagine an interaction in DrScheme:
> (lookup ADDRESS-BOOK 'Adam) 1 > (lookup ADDRESS-BOOK 'Dawn) false > (add-to-address-book 'Dawn 4) > (lookup ADDRESS-BOOK 'Dawn) 4
The first two confirm that 'Adam
has the phone number 1
and that we don't have a phone number for 'Dawn
. The third one adds
the phone number 4
for 'Dawn
to
ADDRESS-BOOK
. And the last interaction shows that the very same
use of lookup
as before now produces the expected phone number.
In the past, the only way we could have achieved this same effect is by
editing the definition of ADDRESS-BOOK
. But, we do not wish users
to edit our programs. Indeed, they shouldn't even have access to our
programs. We are therefore forced to provide an interface with a function
that permits such changes. We could go even further and implement the
graphical interface of figure 95. A dialogue equivalent to the
above interaction would proceed as follows:
Type Adam into the text field, click the Lookup button, and ``1'' appears in the lower text field.
Enter Dawn into the text field, click the Lookup button, and some message concerning a missing number appears in the lower text field.
Replace the message with ``4'' and click the Add button.
Erase the ``4'' from the lower text field, click the Lookup and the ``4'' shows up again.
In short, providing a convenient interface to a user forces us to develop a program whose functions know about each other's usage history.
|
The second example, a traffic light simulation, illustrates how a single
function may need to have some memory. Recall the function next
from exercise 6.2.5. It consumes the current color of a
traffic light and, with the help of clear-bulb
and
draw-bulb
, switches the state of the traffic light on a canvas to
the next traffic color. The result is the next color.
A user who wishes to switch the traffic light four times in a row must enter
(next (next (next (next 'red))))
into the Interactions window. An even more convenient user interface, however, would provide a button that the user can click.
Providing a button means providing a call-back function that somehow knows
about the current state of the traffic light and changes it. Let's call
this function next
, too, but let's assume that it consumes no
arguments. Here is an imaginary interaction using this function:
> (next) > (next) > (next)
Every time we apply next
to no arguments, it produces
the invisible value and simulates the switch of state in the traffic light on
the canvas. In other words, the canvas cycles through the three states
depicted in figure 96. Equivalently, we can have a
user click the ``NEXT'' button three times, which would apply the next
function and have the same visual effect. To accomplish this effect,
the use of next
must affect its own future uses.
|
The final example concerns the hangman
game, which is also the
subject of section 6.7. The game program requires us to
develop three functions: make-word
, reveal
, and
draw-next-part
. We start the game by evaluating
(hangman make-word reveal draw-next-part)
which picks a word, creates the graphical user interface of the lower half
of figure 97, and draws the left-most picture in the
sequence of the upper half of the figure. The player can then choose a
letter from the choice menu in the GUI and click on the ``Check'' button to
determine whether the letter occurs in the word. If so, the
hangman
function reveals where the letter occurs; if not, it uses
our draw-next-part
function to draw the next stage in the hangman
picture. The more bad guesses the player makes, the more of the stick
figure appears in the picture (see top-half of
figure 97).
Our description suggests that the hangman
function in the
teachpack employs a callback function for the ``Check'' button. Let's call
this function check
. It consumes the letter and produces
true
if the check reveals new knowledge:
> (check 'b) true
If not, because the letter has already been guessed, the function produces
false
to indicate that the player didn't gain new knowledge:
> (check 'b) false
In this case, check
also employs draw-next-part
to draw
another part of the hangman figure. Of course, to accomplish this,
hangman
and check
must have some memory about how often
the ``Check'' button was used and how often it was used with a negative
result.
With our current knowledge of Scheme, we cannot formulate functions such as
add-to-address-book
, next
, or check
. To fill
this gap in our knowledge, the next section introduces
set!
67
expressions. This new form of expression permits functions to change the
value that a define
d variable represents. Using this new
construct, we can formulate Scheme functions that have memory. That is, we
can define functions that know something about their history and the
history of other functions.
67 This keyword is pronounced set-bang.