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

  1. April 12th, 2008 | 6:14 pm

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

  2. April 12th, 2008 | 6:37 pm

    TRACE only works for a specific function.

    However, WALK could actually be designed to make use of trace.

  3. April 12th, 2008 | 9:05 pm

    Sometimes I wonder why I bother continuing to try to learn Lisp, then I read stuff like this. This is really cool.

  4. April 12th, 2008 | 9:30 pm

    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.

  5. Robert Goldman
    April 13th, 2008 | 4:26 pm

    One useful extension would be to augment the walker to handle forms that return multiple values….

  6. April 22nd, 2008 | 6:23 pm

    This is great! I’m tired of using format function for debugging.

Leave a reply