Search code examples
common-lisp

(How) can a handler set the return value of signal function?


From SBCL's documentation, we can learn:

Invokes the signal facility on a condition formed from DATUM and ARGUMENTS. If the condition is not handled, NIL is returned.

It does not elaborate on what signal returns in case of a handled condition. So, I assumed (guessed), that the handler can somehow determine the return value of the signal function:

(defun countdown (&optional
            (start-value 10)
            (fail-value 1))
  (loop
    repeat start-value
    for i from 0
    collecting
    (if (= i fail-value)
        (signal "fail-value (~D) hit!" i)
        i)))
(defun countdown-progress-indicator-style (&optional
                         (start-value 10)
                         (fail-value 1))
  (handler-bind
      ((simple-condition (lambda (c)
               (format t "fail (~A)~%" c)
               fail-value))) ;; <--- I hoped the return value of the handler is returned by signal!
    (countdown start-value fail-value)))

But: even if handled, signal returns nil.

(countdown-progress-indicator-style)
fail (fail-value (1) hit!)
(0 NIL 2 3 4 5 6 7 8 9)

Hence, my question, if there is a function or mechanism I missed, which allows the handler to influence signal's return value.


Solution

  • I think the CLHS explains it nicely: in order to handle a condition, you need to transfer control. This can be all kinds of things, but the usual way is to invoke a restart.

    (defun countdown (&optional
                       (start-value 10)
                       (fail-value 1))
      (loop repeat start-value
            for i from 0
            collecting
            (restart-case
                (if (= i fail-value)
                    (signal "fail-value (~D) hit!" i)
                    i)
              (use-fail-value (fv)
                fv))))
    
    (defun countdown-progress-indicator-style (&optional
                                                (start-value 10)
                                                (fail-value 1))
      (handler-bind
          ((simple-condition (lambda (c)
                               (format t "Handling simple condition~%")
                               (apply #'format t
                                      (simple-condition-format-control c)
                                      (simple-condition-format-arguments c))
                               (invoke-restart 'use-fail-value
                                               (first (simple-condition-format-arguments c))))))
        (countdown start-value fail-value)))
    

    This somewhat abuses simple-conditions arguments; you should probably make your own condition, which takes the value explicitly.