Go to the first, previous, next, last section, table of contents.

Interactively Changing a Program (Hunk N)

==================================================================
Hunk N starts here:
==================================================================

Replacing Procedure Values

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.)

Loading Code from a File

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.

Loading and Running Whole Programs

[ to be written ]


Go to the first, previous, next, last section, table of contents.