I've been spending the past couple weeks thinking about errors. This
is one thing we haven't really explored much. Currently, all that we
really do is just error out, but that's not really a great solution.
So, while I don't really have a solution yet, I figure we should
probably start a dialog on how we should do things. So, some
definitions. I found a nice list here:

http://groups.google.com/group/fa.haskell/msg/77dab6e52691fa9c

1. Expected error: The result can't be computed from this data; the
caller was not supposed to check this beforehand because it would
duplicate work of the function, or there would be a race condition, or
the design is simpler that way.

2. Program error: The given function was not supposed to be invoked in
this way by other parts of the program (with these arguments, or with
this external state, in this order wrt. other functions etc.).

3. Out of resource: The function is sorry that it was not able to
produce the result due to limited memory or arithmetic precision or
similar resources. "Not yet supported" is also in this group.

4. Impossible error: The function thought this couldn't happen, it
must have a bug. This is like 2 in that there is a bug somewhere, but
like 1 in that the calling code is probably fine.

Next are a couple common mechanisms for dealing with errors:

1. Abort: Just kill the execution of the program.

2. Signals: An (a)synchronous interrupt that calls a function handler
when a certain event occurs.

3. Return values: A special value is returned when an error occurs.

4. Unchecked exceptions: A runtime signaling of an error that walks up
the stack to find a exception handler.

5. Checked exceptions: An unchecked exception that is statically
checked to find an exception handler.

6. Continuations: A passed in closure that you call when the error occurs.

7. Conditions: Similar to an exception, except that the handler can
tell the condition signaler how to recover.


So with this, I think we can structure a bit on how we can design how
deal with this problems. First, lets critique a couple of the
handlers:

1. Abort: Not really a practical solution. Abort is really only
appropriate for unrecoverable errors. This may or may not be
recoverable, so we cannot make the decision for the end user.

2. Signals: Really only used for the kernel to talk to us, and it's
generally a pain to deal with.

3. Return Values: Using unions we can force people to deal with
errors. Too much error handling can get messy though, but things like
monadic combinators and sugar
can help.

4. Unchecked exceptions: As opposed to return codes that you have to
deal with, exceptions you might not even know that it might occur. You
also lose the locality of the error, and it might be too late to
recover.

5. Checked exceptions: Now you know what a function can through, but
things get really messy with evolving code.

6. Conditions: Don't have too much experience with them so I'm not
sure what the problems are :)


Finally, I'd like to try to exploit some of the interesting features
of felix, such as our unions, fibers, and threads. These provide some
nice primitives that we could build from.

One of the early suggestions was that we provide two interfaces for
fail-able functions: one that returns an opt value, and one that
aborts if the function fails. Another was to use a fiber and pass in
an "error channel" to a function, and if an error occurred, write the
error into the channel. What if we were able to combine all of these?

I've always imagined felix in the more
traditional-small-number-of-threads view, but I've been reading a book
on erlang, and have been very inspired by it. I love the idea of
having tons of independent processes working and managing the health
system. One interesting aspect to erlang is how they do error
handling. While they still do unchecked exceptions for internal
errors, if an error isn't handled, the process is killed and then an
"exit" signal is sent to all the linked processes. These processes can
either catch the exit signal and handle it, or let themselves be
killed by the signal. Here's some of the docs:

http://erlang.org/course/error_handling.html

What if we were able to do something like this? While we might be able
to do this with runtime modification, it might also be able to do this
with monads. Consider monadic exceptions. This means that writing
something like this:

try
  val x = do_something ();
  do_something_else x;
with
  | ?e => handle_error e
endtry;

Could just be a series of function calls like this:

match do_something () with
  | Failure ?e => handle_error e
  | Ok ?x =>
      match do_something_else x with
      | Failure ?e => handle_error e
      | Ok () => ()
      endmatch
endmatch;

We could use this to simulate what erlang does. Say we spawn off a
function that calls some possibly exceptional code. If the code isn't
handled in a monad, then the thread would error out, and the exception
would be written across the channel to the linked processes. Then, if
*those* processes don't handle the exit, it would be passed down the
line.

Working this all out is try, though. Erlang gets away with a lot
because it's a dynamic language. We'd have to figure out some way of
wrapping the values unobtrusively since the type expressions could get
pretty ugly:

schannel[failure[process_t, failure[error_t, ok_t]]]

if you wanted to represent a process that can write exceptions down a
channel, but then itself can error out. I'm not sure how to solve this
other than through type inference.

What do you guys think?

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to