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 thenexec(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 callp.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.