Ideally, such mistakes would never occur in the first place, and the interactive design of Scheme helps to prevent them by encouraging a programming style in which each new user-defined procedure is tested thoroughly before it is added to a program. It is usually straightforward to perform a large variety of tests on a new procedure and to confirm that each test has the desired result. Often it is useful to copy the invocations that you use to test a procedure into the comment that precedes and documents the source code for that procedure, so that they can be re-run easily if the procedure is later modified or adapted for use in a different context.
Even so, programmers are human, and it is not uncommon for a bug somehow to pass through the testing process without being found and corrected, in which case it may be discovered only at a much later stage, when discrepancies are detected in a large program that may include dozens or hundreds of other user-defined procedures. Moreover, even when the programmer discovers during testing that something is wrong with a procedure he has written, it is not always obvious whether the problem lies in the new procedure or in some previously written procedure that it invokes, so finding and correcting a bug is sometimes quite laborious.
Novice programmers sometimes approach the task of finding and correcting a bug by trial and error, making successive small changes in the source code (``tweaking'' it), and reloading and re-testing it after each change, without giving much thought to the probable cause of the bug or to how making the change will affect its operation. This approach to debugging is ineffective, for two reasons:
You may have noticed that when the Chez Scheme interactive interface detects an error, it prints out a message that ends with the line
Type (debug) to enter the debugger.What this means is that Chez Scheme has saved a lot of information about the error and its context and is inviting you to investigate. If you decline this invitation, Chez Scheme will eventually discard the information and re-use the part of the computer's memory in which it was stored. If you accept it, here's what will happen:
> (debug) debug>Here the prompt debug> indicates that your next command will be read and processed by the Chez Scheme debugger rather than by the usual interactive interface. The debugger has by comparison a very limited repertoire of abilities, and we'll investigate only three possibilities:
debug> i #<system continuation in error> :It presents you with the not-very-enlightening statement that when the error was reported, Chez Scheme was executing a procedure named error and was about to continue by restarting the ``system'' -- generating a new prompt and waiting for more input. The colon near the right side of the window is the inspector's prompt; the inspector is waiting for instructions on what you'd like to see at this point.
The inspector arranges the information about the context of the error into frames, one frame for each procedure that had been invoked but had not yet returned a value when the error occurred. The inspector command depth will tell you how many such frames there are, and sf (``show frames'') will give you a list of them.
(define quot-and-rem
(lambda (dividend divisor)
(list (quotient dividend divisor)
(remainder dividend divisor))))
This procedure takes two arguments, both integers, and divides the first
one by the second; it returns a list of two integers, the quotient and the
remainder resulting from the division. Here it is in action:
> (quot-and-rem 38 5) (7 3) > (quot-and-rem -110 12) (-9 -2) > (quot-and-rem 53 0) Error in quotient: undefined for 0. Type (debug) to enter the debugger.No problem with the first two examples, but in the third one we indirectly asked for a division by zero and got an error message.
If you do this correctly, you should see the following list:
0: #<system continuation in error> 1: #<system continuation in quotient> 2: #<continuation in quot-and-rem> 3: #<top level continuation>At the ``top level,'' we typed in a call to
quot-and-rem; that
procedure was invoked but never returned, because the error occurred before
the value was computed. The quot-and-rem procedure invoked
quotient to get the first item for the list; the
quotient procedure also never returned, because it couldn't
complete a division by zero. Instead, it invoked the error
procedure, which stored all this information away, printed out the
appropriate error message, and turned things back over to the Chez Scheme
interactive interface; technically, error hasn't returned yet
either.
quotient
procedure, which is one frame down from ``system continuation in
error,'' let's look at that frame. To descend to that frame,
type the inspector command d (``down'') at the inspector prompt:
#<system continuation in error> : d #<system continuation in quotient> :Then type
s (``show'') to get the information stored in this
frame:
#<system continuation in quotient> : s continuation: #<continuation in quot-and-rem> free variables: 0: 0 1: 53 2: #<system procedure quotient>The ``continuation'' entry identifies the frame for the procedure that invoked the one you're currently looking at. The list of ``free variables'' gives you the values of the parameters for the procedure invocation, starting at the right end: parameter #0, which is
divisor, had the value 0, and parameter #1, which is
dividend, had the value 53. The value listed here as #2 is
the procedure that was invoked. Remember that in Scheme a procedure is
also a datum stored in a variable; in this case it is a ``system
procedure'' (that is, a predefined procedure of Chez Scheme) stored in the
variable quotient.
quot-and-rem procedure:
#<system continuation in quotient> : d #<continuation in quot-and-rem> : s continuation: #<top level continuation> procedure code: (lambda (#:dividend #:divisor) (list (...) ...)) call code: (quotient #:dividend #:divisor) free variables: 0. divisor: 0 1. dividend: 53This time the ``continuation'' entry tells you that
quot-and-rem was invoked directly from the top level. Since
quot-and-rem is a user-defined procedure rather than a
built-in one, the inspector can show you a version of the
lambda-expression used to define it. Because that
lambda-expression was too long to fit on a single line, the inspector
abbreviated it by substituting three dots for some of the subexpressions.
You can ignore the #: prefix on the parameters; this is just a
mechanism that Chez Scheme uses internally to make sure that parameters
don't get mixed up with any similarly named predefined variables.
The ``call code'' entry reproduces the part of the
quot-and-rem procedure in which the error occurred; as we
already know, it occurred inside the call to quotient.
Finally, the ``free variables'' entry lists the values of the parameters of
the quot-and-rem procedure. This time the names of the
parameters are known to the inspector (frames for user-defined procedures
contain a lot more information that frames for predefined ones, for
technical reasons), and the procedure itself is not listed in this entry
because it was fully described in the ``procedure code'' entry.
The inspector command u (``up'') can be used to move up to the next higher frame if you want to review it, and t (``top'') can always be used to return to the top frame, the one at which you entered the inspector.
(multi-juggle '(jump quick spot)) and notice
that an error occurs before any value is returned.
multi-juggle
procedure still doesn't return the correct value. (Unfortunately, it is
commonplace in programming to find that one bug conceals the existence of
another.) Study the program and either revise it until it is correct or
discard it and write different procedures to do the same job. The latter
is often the best course of action when bugs seem especially persistent and
elusive.
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/debugging-in-Chez-Scheme.html