by uriel.
A recovery block is a flow control form that evaluates a sequence of chunks of code stopping at the first one that causes a condition to be satisfied. Each alternative is evaluated with the same starting state. If none of the alternatives satisfies the condition: a form is evaluated to raise an error or return a default value.
Recovery blocks are described in: ''Recovery blocks in action: a system supporting high reliability'', Anderson and Kerr, Computing Laboratory, University of Newcastle upon Tyne, U.K.
This page presents a Guile module implementing a version of recovery blocks in which the initial state is a set of local bindings.
The format of a recovery block is:
(recovery-block BINDINGS
(ensure CONDITION
(by ALTERNATE-FORMS-1)
(else-by ALTERNATE-FORMS-2)
(else-by ALTERNATE-FORMS-3)
(else ELSE-FORMS)))
notes:
the execution flow is:
Let's say that the BINDINGS are ((a 1) (b 2)), the alternate forms are transformed into:
((lambda (a b)
(values (begin ALTERNATE-FORMS) CONDITION)) a b)
so the forms are allowed to mutate the local variables, but not the values themselves.
(define-module (recovery-block) #:use-module (srfi srfi-11) #:export (recovery-block) #:re-export (let-values)) ;; ------------------------------------------------------------ ;; The minimum is: ;; ;; (ensure CONDITION ;; (by FORMS) ;; (else FORMS))) (define (assert-ensure-form-length ensure-form) (if (> 4 (length ensure-form)) (scm-error 'wrong-type-arg 'recovery-block "expected at least 3 element in 'ensure' form, got ~A" (list (length ensure-form)) #f))) (define (assert-first-atom-is-ensure ensure-form) (if (not (eq? 'ensure (car ensure-form))) (scm-error 'wrong-type-arg 'recovery-block "expected 'ensure' symbol, got ~A" (list (car ensure-form)) #f))) (define (assert-second-atom-is-condition ensure-form) (if (not (list? (cadr ensure-form))) (scm-error 'wrong-type-arg 'recovery-block "expected condition as second atom of ensure form, got ~A" (list (cadr ensure-form)) #f))) (define (assert-elements-are-alternates alternates) (for-each (lambda (elm) (if (> 2 (length elm)) (scm-error 'wrong-type-arg 'recovery-block "expected list of at least 2 elements, got ~A" (list elm) #f)) (case (car elm) ((by else-by else) #t) (else (scm-error 'wrong-type-arg 'recovery-block "expected 'else-by' or 'else' as first atom of each alternate, got ~A" (list (car elm)) #f)))) alternates) (if (not (eq? 'by (caar alternates))) (scm-error 'wrong-type-arg 'recovery-block "expected 'by' as first atom of first alternate, got ~A" (list (caar alternates)) #f)) (if (not (eq? 'else (caar (reverse alternates)))) (scm-error 'wrong-type-arg 'recovery-block "expected 'else' as first atom of last alternate, got ~A" (list (caar (reverse alternates))) #f))) ;; ------------------------------------------------------------ ;; (recovery-block ((a 1) ;; (b 2)) ;; (ensure CONDITION ;; (by FORMS) ;; (else-by FORMS) ;; (else-by FORMS) ;; (else FORMS))) (define-macro (recovery-block BINDINGS ensure-form) (define (make-node VARS FORMS CONDITION TAIL) `(let-values (((form-result condition-result) ((lambda ,VARS (values (begin ,@FORMS) ,CONDITION)) ,@VARS))) (if condition-result form-result ,TAIL))) (assert-ensure-form-length ensure-form) (assert-first-atom-is-ensure ensure-form) (assert-second-atom-is-condition ensure-form) (assert-elements-are-alternates (cddr ensure-form)) (let* ((CONDITION (cadr ensure-form)) (VARS (map car BINDINGS)) (OUT (fold (lambda (forms tail) (make-node VARS forms CONDITION tail)) (append '(begin) (cdar (reverse ensure-form))) (cdr (reverse (cddr ensure-form)))))) `(let ,BINDINGS ,OUT)))
(use-modules (recovery-block)) ;; ------------------------------------------------------------ (define (gee-print-catched-error key . args) (if (null? args) (format (current-error-port) "error key: ~A~%" key) (format (current-error-port) "~A: ~A~%" (car args) (let ((message (cadr args)) (format-list (caddr args))) (if format-list (apply format #f message format-list) message))))) (define-macro (test DESCR FORM) `(begin (format #t "~%* ~A:~%~%" ,DESCR) (catch #t (lambda () (let ((result ,FORM)) (format #t "*** result: ~A~%" result))) gee-print-catched-error))) ;; ------------------------------------------------------------ (test "Condition satisfied by 'by' form" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 1) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "Condition satisfied by first 'else-by' form" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 2) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "Condition satisfied by second 'else-by' form" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 3) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "Go down to 'else' form" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 5) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "No 'else-by' alternates, condition satisfied by 'by' form" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 1) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "Format error: no 'by' alternate" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 1) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r) (else (format #t "else alternate: a=~A, b=~A, r=~A~%" a b r) 0)))) ;; ------------------------------------------------------------ (test "Format error: no 'else' alternate" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (ensure (= r 1) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r)))) ;; ------------------------------------------------------------ (test "Format error: missing 'ensure' symbol" (recovery-block ((a (begin (format #t "evaluating 'a' value~%") (+ 1 2))) (b (begin (format #t "evaluating 'b' value~%") (+ 3 4))) (r #f)) (cond (= r 1) (by (format #t "by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 1) r) (else-by (format #t "first else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 2) r) (else-by (format #t "second else-by alternate: a=~A, b=~A, r=~A~%" a b r) (set! r 3) r))))