Non Local Exits

32 views
Skip to first unread message

Matthew D. Swank

unread,
Oct 16, 2008, 10:16:14 AM10/16/08
to Clojure
So, I'm trying to clear up some flow control issues in my head, and I
want to confirm that my thinking is at least in the right direction.

Let's say I implement a very simple restart facility with the
following style:

(def current-handler)
(def current-restart nil)

In some code I want to handle I put:

(binding [current-handler (fn [e] (if current-restart (current-
restart) <some non-local exit>))]
<stuff>)

Later on in the same dynamic extent as <stuff> I put:

(binding [current-restart (fn [] 42)]
(try <dodgy-expr>
(catch Throwable t (current-handler t))))


What can <some non-local exit> be?

In common lisp (if it didn't have a condition system) I could do
something like:
(block my-block
(let ((*current-handler*
(lambda (e)
(if *current-restart* (funcall *current-restart*) (return-
from my-block e)))))
<stuff>))

of in scheme (with fluid-let)

(call/cc
(lambda (my-esc)
(fluid-let ((*current-handler*
(lambda (e) (if *current-restart* (*current-
restart*) (my-esc e)))))
<stuff>)))

Anyway you get the idea; I can unconditionally take an escape
continuation.

However, in clojure/java given:
(binding [current-handler (fn [e] (if current-restart (current-
restart) <some non-local exit>))]
<stuff>)

The only thing I can do for <some non-local exit> is to throw (or re-
throw):

(try (binding [current-handler (fn [e] (if current-restart (current-
restart) (throw e)))]
<stuff>)

;;However, even if there is something to catch it...
(catch Throwable t <more stuff>))

There is no guarantee the catch block will ever see it, and there is
no way to unconditionally abort the rest of the computation from the
point of <some non-local exit> to the return of <stuff>. Is that
correct?


Matt

Rich Hickey

unread,
Oct 16, 2008, 8:48:36 PM10/16/08
to Clojure


On Oct 16, 10:16 am, "Matthew D. Swank" <akopa.gmane.pos...@gmail.com>
wrote:
First off, catching and eating Throwable is bad practice. If your code
instead catches Exceptions, then Errors will throw through untouched.
While there is no direct here-to-there non-local flow control, one
possible mechanism is to:

Declare your own derived class of Error.

Pre-allocate an instance of it in your handler-binding mechanism,
throw that instance as some-non-local-exit, making the original
exception its cause.

Catch your derived Error type and check for that specific instance in
a try block surrounding <stuff> - if it's that instance, it's that
escape, else rethrow.

Rich
Reply all
Reply to author
Forward
0 new messages