DEBUGGING WITH SCM by Gary T. Leavens Department of Computer Science, Iowa State University File $Date: 1997/08/20 02:41:25 $ This document describes how to use the debugging facilities of the SCM interpreter. Aside from trace, I seldom use more advanced debugging facilities. I find it more helpful to hand-simulate my program, or to think about what it is doing instead of using the Portable Scheme Debugger (PSD). When necessary, I usually add some displayln statements (see section 2.5 of "Scheme and the Art of Programming", which is on reserve). You can do this too, as it saves you from the trouble of learning the PSD system. Besides avoiding complications, you may note that the technique of adding print statements is language independent (it will work for C++, Pascal, etc. too). However, as always, more powerful tools are often useful, and using "trace" (see 1.2 below) is faster than adding print statements. 1. WHAT HAPPENED? Consider the following (buggy) Scheme procedure. (define member? (lambda (item ls) (or (member? item (cdr ls)) (equal? (car ls) item)))) Let's try it out. > (member? 3 '(1 2 3)) ERROR: cdr: Wrong type in arg1 () Here's the basic thing an interpreter should do for you, give you information about what happened. First, of course, look at the error message. Note first of all, that it says that the procedure "cdr" was called with a type of argument it didn't expect. The value of the argument is (). This is the same thing that happens when you type the following. > (cdr '()) ERROR: cdr: Wrong type in arg1 () Looking at the error message from the expression (member? 3 '(1 2 3)) thus may be enough to tell you what happened. At some point to fix the bug you will have to use this information to deduce how the error could have occurred. But you may ned more information. 1.1 TRACING One help, which saves you time in putting in print statements, is the SCM trace procedure. This is automatically available for those using the scm342 interpreter. (If not, type (require 'trace) first.) Let's try it. > (trace member?) # > (member? 3 '(1 2 3)) "CALLED" member? 3 (1 2 3) "CALLED" member? 3 (2 3) "CALLED" member? 3 (3) "CALLED" member? 3 () ERROR: cdr: Wrong type in arg1 () The first call, (trace member?) told Scheme to trace all calls to the procedure member?, which was the argument of trace. Next we called (member? 3 '(1 2 3)), which starts running, and shows us the trace. The tracing first shows us the call again: (member? 3 (1 2 3)) but note that the list (1 2 3) is not quoted. What you are seeing is the value of the second argument to member?, which is (1 2 3). To be clearer, consider the following. > (define test-list '(1 2 3)) # > test-list (1 2 3) > (member? 3 test-list) "CALLED" member? 3 (1 2 3) "CALLED" member? 3 (2 3) "CALLED" member? 3 (3) "CALLED" member? 3 () ERROR: cdr: Wrong type in arg1 () See how the value of test-list is shown in the first line of the trace? "CALLED" member? 3 (1 2 3) Now let's look at the next line. "CALLED" member? 3 (2 3) It too shows a call to member, this time a recursive call. The nesting level is shown by the indentation. This call has a smaller list for its second argument, the list (2 3). Similarly, the next call has (3) as its second argument. And the last call before the error has () as its second argument. Exercise: Looking back at the definition of "member?", can you explain what caused the error now? We can get still more information by tracing procedures that member? calls. For example, lets trace cdr. > (trace cdr) # > (member? 3 test-list) "CALLED" member? 3 (1 2 3) "CALLED" cdr (1 2 3) "RETURNED" cdr (2 3) "CALLED" member? 3 (2 3) "CALLED" cdr (2 3) "RETURNED" cdr (3) "CALLED" member? 3 (3) "CALLED" cdr (3) "RETURNED" cdr () "CALLED" member? 3 () "CALLED" cdr () ERROR: cdr: Wrong type in arg1 () It should be pretty clear what's happening now, so let's just explain a bit more of the trace output. The first 3 lines "CALLED" member? 3 (1 2 3) "CALLED" cdr (1 2 3) "RETURNED" cdr (2 3) show the first call to member?, and within member? a call to cdr, with argument (1 2 3). This call to cdr returns the list (2 3). The indentation tells you that the call to (cdr (1 2 3)) happened ``inside'' the call of (member? 3 (1 2 3)); that is, after the call of (member? 3 (1 2 3)) started and before it returned a result. Nothing interesting happened inside the call of cdr, so we just saw its result. If you are seeing too much happening (a common problem in debugging) you may want to stop tracing certain procedures. This can be done using untrace. (Note that untrace is calling cdr, and until it's done, we see the effects of the still existing trace to cdr.) > (untrace cdr) "CALLED" cdr (untrace cdr) "RETURNED" cdr (cdr) "CALLED" cdr ((cdr . #[proc]) (member? . #[proc])) "RETURNED" cdr ((member? . #[proc])) "CALLED" cdr (cdr . #[proc]) "RETURNED" cdr #[proc] # > (member? 3 test-list) "CALLED" member? 3 (1 2 3) "CALLED" member? 3 (2 3) "CALLED" member? 3 (3) "CALLED" member? 3 () ERROR: cdr: Wrong type in arg1 () You can untrace all traced procedures by calling untrace without any arguments. > (untrace) (member?) > (member? 3 test-list) ERROR: cdr: Wrong type in arg1 () Let's summarize trace and untrace (trace proc-name) turns on tracing of procedure named proc-name (untrace proc-name) turns off tracing of procedure proc-name (untrace) turns off all tracing 1.2 DEBUGGING If you use emacs, there is a debugger you can use. It's called PSD (for Portable Scheme Debugger). To find out how to use it, point your web browser at the URL http://www.cs.iastate.edu/~leavens/ComS342/docs/psd/quick-intro/quick-intro.html There is more infomation in the following URLs. http://www.cs.iastate.edu/~leavens/ComS342/docs/psd/article/article.html http://www.cs.iastate.edu/~leavens/ComS342/docs/psd/manual/manual.html