Restarts

Restarts separate *deciding how to recover* (made by high-level code) from *knowing how to recover* (offered by low-level code). The low-level code establishes restarts; a handler higher up chooses one, without unwinding the low-level stack first. This is CL's most distinctive feature.

In the SLIME/Sly debugger you can inspect frames, evaluate code in a frame, fix the bug (redefine a function), and then choose a restart to continue the running program: no restart from scratch.

;; Low-level code: offers recovery strategies
(defun parse-entry (string)
  (restart-case
      (or (parse-integer string :junk-allowed t)
          (error "Bad entry: ~s" string))
    (use-value (value)                          ; restart 1: substitute a value
      :report "Use a different value."
      :interactive (lambda () (list (read)))
      value)
    (skip-entry ()                              ; restart 2: skip this entry
      :report "Skip this entry."
      nil)))

;; Mid-level code: knows nothing about policy
(defun parse-all (strings)
  (remove nil (mapcar #'parse-entry strings)))

;; High-level code: chooses the policy
(handler-bind ((error (lambda (c)
                        (declare (ignore c))
                        (invoke-restart 'use-value 0))))
  (parse-all '("1" "x" "3")))                   ; (1 0 3)

(handler-bind ((error (lambda (c)
                        (declare (ignore c))
                        (invoke-restart 'skip-entry))))
  (parse-all '("1" "x" "3")))                   ; (1 3)

;; Without a handler, the interactive debugger shows the restarts:
;;   0: [USE-VALUE] Use a different value.
;;   1: [SKIP-ENTRY] Skip this entry.
;;   2: [RETRY] Retry e
…
;; Standard restart functions (with matching restart names)
(invoke-restart 'continue) (continue)
(invoke-restart 'use-value v) (use-value v)
(invoke-restart 'store-value v) (store-value v)
(abort) (muffle-warning)
(compute-restarts)                              ; the list of currently available restarts
(find-restart 'skip-entry)                      ; check whether a restart is available
(with-simple-restart (skip "Skip this file.") (process-file f))   ; a quick restart that returns NIL, T

;; Retry pattern
(defun fetch-with-retry (url)
  (loop
    (restart-case (return (fetch url))
      (retry ()
        :report "Retry the request."
        nil))))                                 ; loop around and try again