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