2017-12-08

Exceptional measures

 The story so far


An earlier blog post introduced the process monad and the TBC (Take Back Control) library that is based on it. Read that first.

This entry outlines a few features added since that post was written.

Exceptions and the new .go method

I prefer not to program with exceptions. However, it was clear that the design of the TBC library shouldn't ignore that they do exist. First off, even code that is not intended to throw exceptions might do so if it's not coded correctly. Secondly, we need to be able to interact with libraries written by other people that perhaps are intended to throw exceptions. With the original design of TBC, any exceptions were ignored and potentially lost. For example, if f is a function that throws an exception then

exec(f).go(k) 

will throw an exception. However

(pause(1000) > exec(f)).go(k) 

will not throw an immediate exception; one second later, though, an exception will be thrown that our code can not catch. What happens to it depends on how the event dispatch loop deals with uncaught exceptions. On a web browser, for example, uncaught exceptions are simply ignored.

To make it possible to deal with all exceptions, the .go method now has a second argument. This one indicates what to do with an exception that is thrown while executing the process. If p is a Process<A> and k is a function from A to Void, and h is a function from Dynamic to Void, [Footnote: If you don't know what Dynamic is, don't worry, it will soon be explained.] then we can make the call

p.go(k, h)

If executing p completes normally, k is called with the result as argument. If executing process p throws an exception, h is called with that exception as argument.

In Haxe, exceptions can have any type at all. The type Dynamic in Haxe represents a value that could be of any type whatsoever. This means that exception handling code needs to be prepared for any exception that the process might throw.  If you don't expect any exceptions, a good policy is to at least print each exception to a log. In a browser we might pop up an alert, at least during testing.

Throwing and catching

The second argument to .go allows one to set up a global exception handler. But it doesn't provide a mechanism for a process to recover from an exception and carry on. In many languages (Java, JavaScript, C#, C++, etc.) one can recover from exceptions using a try-catch statement.
 
In TBC a process can recover from an exception and keep going. Exceptions are caught using the static attempt method. [Unless otherwise mentioned static methods are members of the TCB class.]. If  p is a Process<A> and  c is a function from Dynamic to Process<A>, then attempt(p,c) is a  Process<A>. Running attempt(p,c) is the same as running p, except that, if an exception is thrown while running p, c is called with the exception and the resulting process is run.

We can throw an exception using the toss function.  toss(e) is a process that throws exception e. Here e can be an expression of any type at all.

TBC also has the equivalent of Java's finally clauses. An optional third argument to attempt, which should be a Process<Triv>, will be executed regardless of whether there is an exception and whether or not it is handled or re-tossed. The call looks like this attempt(p,c,q). The process q will be run after p has completed normally. If p throws an exception, then q runs after the result of calling c has completed running. If the result of calling c tosses an exception, q will still be run. Assuming the execution of q does not toss an exception, the result (normal or exceptional) of running attempt(p,c,q) will  be the same as the result of running attempt(p,c) would have been.

There is also a function ultimately(p,q) that runs q as a finalizer regardless of whether or not p succeeds or fails. It is equivalent to attempt(p,c,q) where c is the function
function(ex:Dynamic) : Process<A> { return toss(ex) ; }
The behaviours of attempt and ultimately follow the example set by Java's (and JavaScript's) try-catch-finally with one difference: In Java, an exception thrown by the finally part will replace any exception thrown from the catch part or the try part. In TBC, the two exceptions are combined in a Pair, and the Pair is thrown.  In any case it's a bad idea to use a process that might throw an exception as a finalizer.

It's not recommended to use exception handling as part of normal control flow. I included attempt, toss, and ultimately, so that there is a way to cope with exceptions that might be thrown from library code or because of programmer error, not so that programmers are encouraged to throw their own exceptions.

Future posts will discuss loops, ajax calls, and communication on channels.

No comments:

Post a Comment