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