================================================================== Hunk N starts here: ==================================================================
Earlier we showed how to replace normal data values in variable
bindings, using the side-effecting special form set!
.
We can also change procedure values. One way of doing this is just to change the value of the procedure variable. (Remember that a "named" procedure is really just a first-class procedure object that happens to be referred to via a pointer stored in a variable binding.)
Just as we changed the value of the variable myvar
using set!
,
we can change the value of the procedure variable quadruple
.
Try this:
Scheme>(quadruple 3) 12 Scheme>(set! quadruple double) #<procedure> Scheme>(quadruple 3) 6
What happened here is that when we evaluated the expression
(set! quadruple double)
it just did the usual thing set!
does when both of its arguments are variables--it computed the
value of the expression on the right, in this case by fetching
the value from the binding of double
, and stored it into
the (binding of) the variable on the left. In this case, the value
of double
is (a pointer) to a procedure--the one that we
created when we define
'd double
. This pointer was
copied into quadruple
, so that it now contains a pointer to
the very same procedure.
Calling quadruple
now has the same effect as calling double
,
because either way, a pointer is fetched from the variable, and whatever
it procedure it points to is called.
Note that while this illustrates how Scheme works, and we'll show
why it's handy later, it's not usually a great idea to go around changing
the values of procedure variables by side-effecting them with
set!
.
Usually, once a program has been developed, you don't want to clobber named procedures, because it makes the code hard to understand--you don't want your finished program to go around changing the meaning of procedure names as it runs. You normally want to be able to look at your program and see the definitions, and not have to worry that some other part of the program may change the procedures at odd moments.
During interactive development of a program, however, it's often very convenient to be able to change a procedure's behavior at will. We're not really modifying a procedure, though--we're changing a variable binding's value to affect which procedure is called. We don't have to actually modify any procedure objects, because we can replace a pointer to one procedure with a pointer to another.
Usually you'll want to do this by redefining the procedure
with another define
expression.
For example, suppose we want to restore the old behavior of
quadruple
, which we foolishly clobbered above. We can
simply define
it again, the old way:
Scheme>(define (quadruple x) (double (double x))) #void
In a finished program, you generally shouldn't have multiple definitions of
the same thing--a define
form should define something that doesn't
change during program execution. If you want to change the state of
a binding, use set!
to make it clear that's what's going on, and
put a comment at the definition of the variable warning that it is likely
to be changed at runtime.
Most interactive Scheme systems let you define
the same variables
multiple times, though, so that you can change things during program
development. (Note that we're talking about redefining the same program
variable here, not defining different variables with the same name in
different scopes.)
When you're actually developing a program, you often want to save the text in a file, rather than just typing it in and losing it when you exit the Scheme system.
The simplest way of doing this is to use an editor in one window and
Scheme in another. From the editor, save your program text into a
file, and then load it into Scheme with the load
procedure.
load
takes a string as an argument, which is the name of the
file to load, and reads it in just as though you had typed it in
by hand, at the prompt. (A string literal is written with double
quotes around it; there'll be more about strings more later.)
Type the following text into your editor and
save it into a file named triple.scm
.
(define (triple x) (+ x (+ x x)))
Now, at the Scheme prompt, load the file and call the procedure:
Scheme>(load "triple.scm") loading...triple...done Scheme>(triple 3) 9
(Notice that in the above example, there's no connection between the
string we used to name the file, "triple.scm"
, and the name of
the procedure, triple
. We just chose to call the file
"triple.scm"
to remind us what's in it.)
Usually, when you're developing a program, you should put only a few definitions in a file--maybe just one. This lets you change small parts of your program, saved the changed file, and reload the file to change the definitions in your running Scheme system.
Good editors also have packages that allow you to run Scheme and use an editor command to send the contents of a file (or a selected region of a file) to Scheme, as though you'd typed it in. (Emacs has excellent facilities for this.)
If you're using a graphical user interface, you may be able to simply cut text from your editor, and paste it into the window you have Scheme running in, so that it appears to Scheme as though you'd just typed it in.
Be careful about reloading definitions. When you load a file, the Scheme system will reuse the same top-level bindings, and reinitialize them. In general, new objects will be constructed, even if the textual definitions haven't changed.
For example, suppose we have the following code in a file, which we've already loaded once:
(define my-list (list 1 2)) (define my-other-list (cdr my-list))
If we reload this file, all three definitions will be processed again.
A new list will be constructed and the existing binding of my-list
will be updated to point at the new list.
Likewise, the existing binding of my-other-list
will be updated with
the cdr
of that new list. Each time we reload the file, we'll recreate
the intended data structure, including the sharing relationship between
the two lists.
But now consider what happens if this code is spread across two files,
with the definition of my-other-list
in a different file, which
we don't reload. If we just reload the first definition, then the binding
my-other-list
will still refer to the cdr
of the old list,
not the new one. If your code depends on the two lists sharing structure,
it not behave as expected, because the two variables' bindings will refer to
distinct lists.
Procedures can cause the same sorts of problems. If you have a pointer to a procedure in a data structure, and then you redefine the procedure by modifying the definition and reloading it, a new procedure object will be created, but the old data structure will still hold a pointer to the old procedure object.
In general, you should be careful to recreate any data structures holding procedures if you redefine those procedures. This is usually easy, if you reload the code that creates the data structures, after reloading the new definitions of the procedures.
Notice that this is not necessary if you just call top-level procedures (or look up
variable values) in the usual way. For example, given our earlier definitions of
double and quadruple
, changing double
affects quadruple
immediately. Every time we call quadruple
, it fetches the current value
of the binding of double
, which ensures that it sees the most recent version.
We can reload the code for double
, without reloading the code for quadruple
.
[ to be written ]