Goal: Learn about ...
- Sources of Errors
- Kinds of Errors
- Debugging Strategy
- MIT Scheme Debugging Tools
Common Sources of (Scheme) Errors
- Typographical errors (misspellings)
- Syntax errors (parens)
- Errors inside a module (procedure or algorithm)
- Errors connecting modules together (program structure)
Scheme Error Messages, Part 1
- Unbound or unassigned variable
- Either a typographical error, or use of a variable outside
of the part of the program in which it is available (e.g. using
a variable created by let outside the body of the let,
or use of a variable created by define within a procedure
outside of that procedure).
- Applying something that isn't a procedure
- "The object ... is not applicable" The value of
the first subexpression in a combination must be a procedure.
- Wrong number of arguments to a procedure
- The number of arguments in a call to a procedure must
match the number of parameters the procedure expects.
Scheme Error Messages, Part 2
- Wrong type or range of arguments to a primitive
procedure
- Some primitive procedures require a specific type of argument.
For example, the procedure that does addition requires that all
of its arguments be numbers, so (+ 3 *) results in the
error message "The object #[compiled-procedure ...], passed
as the second argument to integer-add, is not the correct type."
- Numerical errors (overflow, underflow, divide by zero)
- The numerical procedures can fail in a number of ways when
dealing with inexact numbers that are either very large, very
small, or very close to zero.
- Other errors
- A number of the procedures built into MIT Scheme can detect
special errors and report them. For example, "Unable to
open file ... because: no such file or directory."
Debugging Strategy
- Don't throw away your state when an error occurs
- Think abstractly to locate a module that is failing
- Examine only modules that fail
- Errors are more common in connecting modules together than
in individual modules
MIT Scheme Debugging Tools
- Debugger
- Pretty printer
- Tracing
- Breakpoints
The Debugger
- Allows you to inspect the state of the computation when an
error occurs.
- Built around subproblems and reductions
- Subproblems are created to evaluate simpler expressions
whose values must be combined, used, or modified to produce the
value of a larger expression
- Reductions are created when a large expression can
be converted into a simpler expression which has precisely the
same value as the large expression.
The Debugger Windows
Two windows in the buffer
- Program history, with most recent information at the top and
oldest at the bottom.
- Evaluation area showing current expression and the values
of "current" variables. New expressions can be evaluated,
but you shouldn't "alter the state" of the computation.
- Useful commands: C-N, C-P go up and down the
history; p allows the computation to continue in many cases;
e shows more details of the values of variables.
Pretty printer
- (pp <procedure>) works for any expression
whose value is a procedure.
- Allows you to see your program as the computer sees it.
- Doesn't just print the original source code of a procedure;
instead it reconstructs what the source might have been from an
internal representation that is actually used to run the procedure.
- This is the best way to catch errors from missing or misplaced
parentheses.
- Good way to learn about MIT Scheme's internals, as well.
The sources are on-line, too. The procedure where lets
you look at the values of "hidden" variables available
to a procedure.
Tracing
- (trace <procedure>) works for any expression
whose value is a procedure. See also trace-entry and
trace-exit.
- Shows when a procedure is called, the arguments to it, and the
value it returns.
- Remove via untrace (or untrace-entry or
untrace-exit). Untrace all procedures using (untrace),
etc.
- Warning: exit tracing "breaks tail recursion" and
turns iterative processes into recursive ones, so you may run out of
memory.
- It is possible to trace a procedure that is defined within another
procedure: (trace fact '(iter)) would trace the procedure
iter that is nested inside the procedure fact. It isn't
possible to trace procedures created using letrec or internal
lambda expressions; only nested procedures given a name through
define.
Breakpoints
- (break <procedure>) works for any expression
whose value is a procedure. See also break-entry and
break-exit.
- The procedure pauses before it starts (for break or
break-entry) and just before returning a value (for
break and break-exit). You are in the interpreter with
all of the variables defined just as they are at the beginning (end)
of the "broken" procedure.
- You can continue the computation normally using (proceed).
At an exit breakpoint, you can return with a different value using
(proceed <value>).
Breakpoints, continued
- The current procedure is available from (*proc*), the
arguments from (*args*), and the result (at the exit of a
procedure) from (*result*).
- Breakpoints are removed using unbreak (or
unbreak-entry or unbreak-exit). All breakpoints can be
removed (from all procedures) using (unbreak), etc.
- Exit breakpoints also "break tail recursion."
- Internal procedures can have breakpoints inserted, as in
trace.