April 12, 2008
Analyzing return values with a recursive macro
Printing the argument and return values of a set of nested or sequential expressions is a common debugging tactic.
In Common Lisp we can make use of a recursive macro instead of manually inserting printing statements. Specifically, we want our macro (let’s call it WALK for want of a better name since it’s a simple code walker) to print all the return values it encounters along its way:
(walk (list (+ 5 (* 3 3)) "Welcome to earth, third rock from the sun!")) (* 3 3) => 9 (+ 5 (* 3 3)) => 14 (LIST (+ 5 (* 3 3)) "Welcome to earth, third rock from the sun!") => (14 "Welcome to earth, third rock from the sun!")
The following macro does that job.
(defmacro walk (form) (etypecase form (atom ; terminating base case form) (cons `(let ((result (,(first form) ,@(mapcar (lambda (arg) `(walk ,arg)) (rest form))))) (format t "~S => ~S~%" ',form result) result))))
Modifying the output so it prints
(* 3 3) => 9 (+ 5 9) => 14 (LIST 14 "Welcome to earth, third rock from the sun!") => (14 "Welcome to earth, third rock from the sun!")
instead is left as an exercise to the reader (bad puns come easily in English…), as is the addition of an optional DEPTH argument.
It would also be nice to make use of the pretty printer for appropriate indentation, but I have severe problems grokking it, so maybe someone familiar with this facility can help.
In other programming languages the same problem requires a bit more effort.
Comments(6)
Hey,
Whats wrong with trace? Admittedly, I *hate* debugging so I try and write code that doesn’t require it, but:
(defun foo (x)
(if (eql x 0) ‘done
(list x (foo (1- x)))))
(trace foo)
(foo 5)
0: (FOO 5)
1: (FOO 4)
2: (FOO 3)
3: (FOO 2)
4: (FOO 1)
5: (FOO 0)
5: FOO returned DONE
4: FOO returned (1 DONE)
3: FOO returned (2 (1 DONE))
2: FOO returned (3 (2 (1 DONE)))
1: FOO returned (4 (3 (2 (1 DONE))))
0: FOO returned (5 (4 (3 (2 (1 DONE)))))
TRACE only works for a specific function.
However, WALK could actually be designed to make use of trace.
Sometimes I wonder why I bother continuing to try to learn Lisp, then I read stuff like this. This is really cool.
This is only a first stab, though. Problems will arise when macros are in the inspected body, and possibly also when special operators are involved.
One useful extension would be to augment the walker to handle forms that return multiple values….
This is great! I’m tired of using format function for debugging.