From: ········@gmail.com
Subject: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <1117314790.726050.132870@f14g2000cwb.googlegroups.com>
Hello Lispers!
I'm pretty new to Common Lisp and macros, and I've been working through
Seibel's book.  For practice, I wrote a macro to save myself a few
keystrokes during debugging, and I'd just like some feedback on it
(like, is it leaky?).

The problem my macro solves: When debugging, it's useful to sprinkle
print statements throughout your code to monitor variables.  In most
languages, you do something like this: print( "end of loop, x=" + x + "
y=" +y ).  Now, wouldn't it be totally awesome if you didn't have to
type x and y twice!?  Like, what if you could do: print( "end of loop",
x, y) and this would print out "end of loop, x=1234, y=5678".  So
that's what my macros let you do (and then some):

(defvar *debug-output* t)	; true will just send it to std-out

(defun self-evaluating-p (form)
  "Taken from Seibel's book"
  (and (atom form) (if (symbolp form) (keywordp form) t)))

(defmacro show-form-and-value (form) `(format *debug-output* "~a = ~a"
',form ,form) )
(defmacro show-self-evaluating (form) `(format *debug-output* "~a"
,form) )

(defmacro show-form-smartly (form)
  "If form is self-evaluating, it just shows the value.  Otherwise, it
shows the form and the value."
  (cond ((self-evaluating-p form)
		 `(show-self-evaluating ,form))
		(t
		  `(show-form-and-value ,form) )))

(defmacro debug-sentence (&rest parts)
  "
  <<<<
  Print a list of forms in a smart manner: If it's self-evaluating
(like a string or number), just print it directly.  If it's a form
(like (+ 1 2)), print the form and evaluation (so it would become (+ 1
2) = 3).
  >>>>
  "
  (let ((out (list)))
	(dolist (part (reverse parts))
	  (push `(format t " ; ") out)
	  (push `(show-form-smartly ,part) out)
	)
	(push 'progn out)
	out ))

Example: (dbl "Let's do some math.." (+ 1 2) (* 2 3) (sqrt 2))
This would print: Let's do some math.. ; (+ 1 2) = 3 ; (* 2 3) = 6 ;
(sqrt 2) = 1.4.. ;

So any comments/suggestions?  Am I doing anything dangerous or just
plain bad?

And if you're out there Mr. Seibel, thank you!  Your book is simply
awesome.  It really demonstrates the unique abilities of Lisp.  I hated
using Scheme/Lisp in my CS classes, since it just seemed like another
language with annoying parentheses and no infix expressions and really
confusing syntax, but I had my epiphany reading your CD database
chapter and was quite blown away :)

Thanks,
Steve

From: ········@gmail.com
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <1117315328.375304.132200@g49g2000cwa.googlegroups.com>
Woops, forgot something:

(defmacro dbl (&rest parts)
  `(progn
	 (debug-sentence ,@parts)
	 (format t "~%")))

Same as debug-sentence - just adds a newline at the end.
From: Wade Humeniuk
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <Ygcme.31027$tt5.2876@edtnps90>
I really have no particular comment about the usefulness of
the macro but here are some coding changes...

(defvar *debug-output* t)

(defun self-evaluating-p (form)
   "Taken from Seibel's book"
   (and (atom form) (if (symbolp form) (keywordp form) t)))

(defun show-form-and-value (form)
   `(format *debug-output* "~a = ~a ; " ',form ,form))
(defun show-self-evaluating (form)
   `(format *debug-output* "~a ; " ,form))

(defun show-form-smartly (form)
   "If form is self-evaluating, it just shows the value.  Otherwise, it
shows the form and the value."
   (cond
    ((self-evaluating-p form) (show-self-evaluating form))
    (t (show-form-and-value form))))

(defmacro debug-sentence (&rest parts)
   `(progn ,@(mapcar #'show-form-smartly parts)))

That all said,..... I would actually take a slightly different approach

(defun debug-expressions (expressions results)
   (when *debug-output*
     (format *debug-output* "(DEBUG-PROGN~%")
     (map nil
          (lambda (expr result)
            (if (eql expr result)
                (format *debug-output* "  ~S~%" expr)
              (format *debug-output* "  ~S -> ~S~%" expr result)))
          expressions
          results)
     (format *debug-output* ")~%"))
   (car (nreverse results)))

(defmacro debug-progn (&rest parts)
   `(debug-expressions ',parts (list ,@parts)))


Then you can use debug-sentence1 to wrap some existing forms in
a function, get the *debug* output *and* still have the
code perform properly. e.g.

(defun stepped-func (n)
   "hello"
   (setf n (- n 10))
   (setf n (/ n 12))
   (incf n))

(defun stepped-func1 (n)
   (debug-progn
    "hello"
    (setf n (- n 10))
    (setf n (/ n 12))
    (incf n)))

CL-USER 16 > (stepped-func 10)
1

CL-USER 25 > (stepped-func1 10)
(DEBUG-PROGN
   "hello"
   (SETF N (- N 10)) -> 0
   (SETF N (/ N 12)) -> 0
   (INCF N) -> 1
)
1



Wade
From: ········@gmail.com
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <1117358394.403625.307100@g49g2000cwa.googlegroups.com>
Ah very cool.  That is indeed a much more useful - thanks!
From: Wade Humeniuk
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <Xicme.31028$tt5.27202@edtnps90>
Wade Humeniuk wrote:

> 
> Then you can use debug-sentence1 to wrap some existing forms in
                   ^debug-progn
From: Alan Crowe
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <86oeasis5g.fsf@cawtech.freeserve.co.uk>
Wade Humeniuk wrote:

     That all said,..... I would actually take a slightly
     different approach

     (defun debug-expressions (expressions results)
	(when *debug-output*
	  (format *debug-output* "(DEBUG-PROGN~%")
	  (map nil
	       (lambda (expr result)
		 (if (eql expr result)
		     (format *debug-output* "  ~S~%" expr)
		   (format *debug-output* "  ~S -> ~S~%" expr result)))
	       expressions
	       results)
	  (format *debug-output* ")~%"))
	(car (nreverse results)))

     (defmacro debug-progn (&rest parts)
	`(debug-expressions ',parts (list ,@parts)))

This is clever, but it doesn't really fit in a /debug/
macro. Consider

CL-USER(11): (defun stepped-func1 (n)
   (debug-progn
    "hello"
    (setf n (- n 10))
    (setf n (/ n 12))
    (incf n)
    (error "Whoops!"))
)
STEPPED-FUNC1
CL-USER(12): (stepped-func1 10)
Error: Whoops!

The error is signalled while drawing up the list of
results. We would have prefered that the results of the
previous lines had already been printed before we attempt
the evaluation of the form containing the error.

Alan Crowe
Edinburgh
Scotland
From: Wade Humeniuk
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <SdSme.25703$wr.13455@clgrps12>
Alan Crowe wrote:

> 
> The error is signalled while drawing up the list of
> results. We would have prefered that the results of the
> previous lines had already been printed before we attempt
> the evaluation of the form containing the error.
> 

Here is a version with more meat.  Whether this much
machinery is actually needed is up for grabs, but to
be safe I think it is.  Of course any mechanism can be defeated
with enough tinkering.

(defvar *debug-output* t)

(define-condition progn-debug-output (simple-condition)
   ((form :initarg :form)
    (multiple-value-list :initarg :multiple-value-list)))

(defun progn-debugger (condition value)
   (declare (ignorable value))
   (typecase condition
     (progn-debug-output
      (with-slots (form multiple-value-list) condition
        (when *debug-output*
          (cond
           ((and (= (length multiple-value-list) 1)
                 (eql form (first multiple-value-list)))
            (format *debug-output* "  ~S~%" form))
           (t
            (format *debug-output* "  ~S -> ~{~S~^,~}~%" form multiple-value-list))))
        (signal condition)))))

(defmacro progn-debug (&body body)
   (let ((completedp (gensym "completed"))
         (values (gensym "values")))
     `(let ((*debugger-hook* #'progn-debugger)
            (,completedp nil))
        (unwind-protect
            (progn
              (when *debug-output*
                (format *debug-output* "(PROGN-DEBUG~%"))
              (let ((,values
                     (progn
                       ,@(mapcar
                          (lambda (form)
                            `(handler-case
                                 (invoke-debugger
                                  (make-condition 'progn-debug-output
                                                  :form ',form
                                                  :multiple-value-list
                                                  (multiple-value-list ,form)))
                               (progn-debug-output (condition)
                                  (slot-value condition 'multiple-value-list))))
                          body))))
                (setf ,completedp t)
                (apply #'values ,values)))
          (when *debug-output*
            (unless ,completedp
              (format *debug-output* "  #<Abnormal termination of PROGN-DEBUG>~%"))
            (format *debug-output* ")~%"))))))

(defun stepped-func2 (n)
   (progn-debug
    "hello"
    n
    (setf n (- n 10))
    (values (setf n (/ n 12)) (incf n))))

(defun stepped-func3 (n)
   (progn-debug
    "hello"
    n
    (setf n (- n 10))
    (error "Flub")
    (values (setf n (/ n 12)) (incf n))))
CL-USER 29 > (stepped-func2 10)
(PROGN-DEBUG
   "hello"
   N -> 10
   (SETF N (- N 10)) -> 0
   (VALUES (SETF N (/ N 12)) (INCF N)) -> 0,1
)
0
1

CL-USER 30 > (stepped-func3 10)
(PROGN-DEBUG
   "hello"
   N -> 10
   (SETF N (- N 10)) -> 0

Error: Flub
   1 (abort) Return to level 0.
   2 Return to top loop level 0.

Type :b for backtrace, :c <option number> to proceed,  or :? for other options

CL-USER 31 : 1 > :a
   #<Abnormal termination of PROGN-DEBUG>
)

CL-USER 32 >
From: Alan Crowe
Subject: Re: Critique my (a newbie's) debugging macro
Date: 
Message-ID: <86r7foishn.fsf@cawtech.freeserve.co.uk>
········@gmail.com asked:
> So any comments/suggestions?  Am I doing anything
> dangerous or just plain bad?

I found your code stylishly written and a pleasure to
read. I particularly liked the way that show-form-smartly is
built up from show-form-and-value and
show-self-evaluating. The basic idea, that I'm going to take
a peek at the form and do things differently depending on
what I make of it, comes through clearly, and the names
"give the game away" as far as the details of what the code
is about. 

CL contains two functions that your code could use,
constantp and mapcan

You could use the built in constantp instead of
self-evaluating-p. It will recognise a quoted form as being
constant, producing

CL-USER(32): (show-form-smartly '(+ 5 7))
(+ 5 7)

instead of 

CL-USER(29): (show-form-smartly '(+ 5 12))
'(+ 5 12) = (+ 5 12)

mapcan is a variant of mapcar

                 (defmacro debug-sentence (&rest parts)
		   (cons 'progn
			 (mapcan
			  (lambda (part)
			    (list '(format t " ; ")
				  `(show-form-smartly ,part)))
			  parts)))

It lets you map over the parts, returning two forms for each
part, and ending up with a flat list, not a list of 2-lists.
Plus it gets the REVERSE out of your code.

Alan Crowe
Edinbrugh
Scotland