screwlisp proposes kittens

ANSI cl condition restart example

| link | Last. 20260125T111553139Z |

Edit: Kent Pitman provided some improvements on the show I edited in per this note 20260201T214912756Z

I promised something on common lisp conditions tonight earlier when my streaming nose and eyes were stopping me from streaming Sunday-morning-in-Europe peertube LIVE live. New short example videos are in order, but in this case I had to get used to conditions myself a little bit. What follows is an example of a condition with a handler that shadows a restart name.

Viz. https://www.lispworks.com/documentation/HyperSpec/Body/m_rst_ca.htm

(define-condition foo () ())
(define-condition bar () ())

(defun has-a-bar->buz (c)
  (declare (ignore c))
  (restart-case
      (handler-bind
	  ((bar #'(lambda (c)
		    (let ((r (find-restart 'buz c)))
		      (when r
			(invoke-restart r c))))))
	(restart-case
	    (signal 'bar)
	  (buz (c)
	    (print "inner buz reached")
	    (print (compute-restarts))
	    (let ((r (find-restart 'buz c)))
	      (print r)
	      (when r
		(invoke-restart r c))))))))

(restart-case
    (handler-bind
	((foo #'has-a-bar->buz)
	 (foo #'(lambda (c)
		  (let ((r (find-restart 'baz c)))
		    (when r
		      (invoke-restart r c)))))
	 (bar #'(lambda (c)
		  (let ((r (find-restart 'buz c)))
		    (when r
		      (invoke-restart r c))))))
      (signal 'foo))
  (baz (c) (declare (ignore c)) (print "baz reached") nil)
  (buz (c) (declare (ignore c)) (print "buz reached") nil))

If you will forgive my printing. Basically "inner buz reached" will print before "buz reached".

"inner buz reached" 
(#<RESTART BAZ {7F78BE3DEA23}> #<RESTART BUZ {7F78BE3DE9E3}>
 #<RESTART SWANK::RETRY {7F78BE3DED53}> #<RESTART ABORT {7F78BE3DF0C3}>
 #<RESTART ABORT {7F78BE3DFAF3}>) 
#<RESTART BUZ {7F78BE3DE9E3}> 
"buz reached" 
NIL

as anticipated.

What had got me about this was that as you, dear reader, noticed I had been imagining invoke-restart as more or less transferring control into a tagbody-go. Now I am slowly appreciating that restart-case actually provides the restart case to begin with with no need for a go to based case construction rewalked from FORTRAN’s history.

But, but but but.

Consider an expression inside a handler-bind inside a restart-case as above. Mentally draw arrows from the expression being evaluated through the handlers bound in order. Then trace arrows from the expression to the meanings of restart names. Of course, everything in its ancestry is also directly available through compute-restarts.

As a supplementary example, binding a restart straight to the level above and putting it at the leftmost highest priority (innermost and leftmost=topmost).

(defun straight-to-frob (c)
  (declare (ignore c))
  (restart-case
      (handler-bind
	  ((foo  #'(lambda (c) (invoke-restart (find-restart 'frob) c))))
	(signal 'foo))))

(restart-case
    (handler-bind
	((foo #'straight-to-frob)
	 (foo #'has-a-bar->buz)
	 (foo #'(lambda (c)
		  (invoke-restart (find-restart 'baz) c)))
	 (bar #'(lambda (c)
		  (invoke-restart (find-restart 'buz) c))))
      (signal 'foo))
  (baz (c) (declare (ignore c)) (print "baz reached") nil)
  (buz (c) (declare (ignore c)) (print "buz reached") nil)
  (frob (c) (declare (ignore c)) (print "frob reached") nil))

The handler priority, i.e. order of opportunity for a handler to either decline or transfer control to a restart is the same order as method composition in the common lisp object system. But having the order of shadowed restarts as well / access to the runtime compute-restarts is very new to me.

My geometric interpretation I am thinking about is each handler-bind/restart-case fractally being a polar coordinates central axis with the linearizable handler priority along the axis and its innermost restarts spreading out from it. The reason I am thinking of it like this is to underscore that this is not just a prog (~ from FORTRAN’s program).

A symbolic view is that we have condition names and restart names, and handlers are the possibly multi-step signal edges from conditions onto restarts.

That restarts are locally computed and not lexical (and being local restarts, we have the pre-made continue, store-value, and use-value locally as well as abort and muffle-warning) creates a unique runtime control dynamic.

screwlisp proposes kittens