Warnings, Signals and Debugging Workflows

Interactive debugging workflow: an error occurs → the debugger shows the backtrace and restarts → inspect locals (e to evaluate in a frame, v to view source, i to inspect), fix the code in your editor and recompile the function (C-c C-c) → restart the frame (r) or choose a restart → the program continues.

Compare with other languages: exceptions unwind the stack before handlers run, which destroys the context needed to recover. CL's handlers run first, with the context intact.

;; Signaling non-error conditions for logging/progress
(define-condition progress (condition)
  ((percent :initarg :percent :reader percent)))

(defun long-task ()
  (dotimes (i 5)
    (signal 'progress :percent (* 20 (1+ i)))    ; no handler → SIGNAL just returns NIL
    (sleep 0.01))
  :done)

(handler-bind ((progress (lambda (c) (format t "~&~a% done~%" (percent c)))))
  (long-task))

;; Debugger entry points
(break "Inspect state here: ~a" 42)              ; enter the debugger deliberately
(invoke-debugger (make-condition 'simple-error :format-control "manual"))
(setf *debugger-hook* (lambda (c hook) (declare (ignore hook)) (format t "Fatal: ~a~%" c) (uiop:quit 1)))
;; *break-on-signals* can stop on any signal of a given type