A brainstorm for your enjoyment, perusal, and general discussion...
SUMMARY A proposal for an extension to the usual exception handling concept. The concept detailed here provides a mechanism for handling exceptions in one of three ways: changing the values being evaluated, setting the result of the entire expression, or throwing an exception as usual. JUSTIFICATION Why do I want to augment how exceptions are handled? Is everything about exceptions already invented? The answer is that I hate how Java handles exceptions. In Java it seems like every time you want to do something, you have to surround it with exception handlers. I find that in Java my code becomes buried in exceptions. Yes, the current every-block-is-a-try-block greatly helps this problem, but there's still the looming prospect of having to write mountains of CATCH blocks to handle every problem. What I propose here is a system that provides a technique for actually fixing the problems on the fly, instead of having to hand code error handling every step of the way. Furthermore, the techniques described here provide a way to make certain behaviors modifiable, so that everybody gets their own favorite flavor of ice cream, so to speak. This concept was inspired by the variety of preferences about how Perl6 should handle 1/0. Please note that this is not a proposal for *changing* how exceptions are handled, it's merely an *addition* to the standard techniques. EXCEPTIONCREATOR CLASS Each block gets a reference to an ExceptionCreator object. By default, blocks inherit their parent's reference, so we won't have to be moving and creating references a lot. When the interpretor gets to an error, it calls the ExceptionCreator's standard method for handling that specific type of exception: divByZero(), stringConcatenationUsingUndef(), whatever. The method for that type of exception calls the constructor of an Exception class that is devoted to handling just that type of exception. The Exception's constructor method, described in more detail on the next section, determines how the exception is handled. To change how certain exceptions behave, a block simply changes the methods of the existing ExceptionCreator to point to other subroutines. This approach allows for an ala carte style of exception configuration. Blocks can (through a module that makes this sort of thing easy), clone the ExceptionCreator object, then change just the methods that are desired. A reference to that new object is passed down the line to child blocks. You can also set the "default" ExceptionCreator reference, i.e. the ExceptionCreator used by default by all blocks in the entire program. By doing so, you can set how certain exceptions are handled by modules that you use but didn't write. For example, suppose I want all string concatenations using undef to be fatal (and I do). I simply override the default ExceptionCreator's concatUndef() method to something more fatal. The following are some of the standard exceptions that the interpretor might throw. I'm sure there a more. These are just the ones that come to mind. - divide by zero - numeric operation on non-numeric value - string concatenation using undef - use of tainted data in protected operation - unsuccesful attempt to open file Modules that provide alternate exception handlers should supply static methods for making the change to a given block. For example, to have div by zero evaluate to Inf instead of a fatal exception, you could simply add this code to your block: use Math::Inf::Exception; and voila, the import routine sets your block's ExceptionCreator for handling div by zero errors. EXCEPTION CLASS Every type of standard exception has its own class. The static constructor method, new, accepts one argument and returns one of three possible values. The input is a reference to the object that caused the exception. This is an optional argument in case no specific object threw the exception. An example of an object that threw an exception would the undef object used in a string concatenation. The constructor returns one of three values: an exception object, the DO_OVER constant, or the EXPRESSION constant. If an exception object is returned, that means that the interpretor should immediately exit the block, throwing the exception to that block's CATCH block, or its parent's CATCH block, and so on outward until somebody catches it. You know the routine. DO_OVER means to reevaluate the expression because the reference to the offending variable has been set to something else. For example, the undef might have been changed to an empty string. The interpretor will only call each type of exception once for each type of object. If the exception constructor just sets the value to another undef, the interpretor will make a nasty gesture are the handler, so "enough of you", and throw its own UnhandledException exception. It must be noted that changing the value of the offending object does NOT mean that the variable in the block is permanently changed. The value is only changed for that particular evaluation of the variable. The constructor simply changes the reference from one object to another, so we don't need to go around cloning objects, we just move references around. EXPRESSION means that the interpretor should stop trying to evaluate the expression, and should use the value returned as the second value in the return array (or however we want to move that value around). For example, a div by zero error would normally return an exception that must be caught somewhere. Programmers who want 1/0 to evaluate to Inf could use an exception constructor that sets the expression to an Inf object. The Inf object would in turn overload all the math operators so the, for example, 1 + Inf is Inf. Note that "the entire expression" is only the three values involved in the division, not the entire mathematical expression. So, for example, this full expression: (1/0) + 3 would trigger the error just for the (1/0) part, which would be evaluated to Inf, thus turning the full expression into: Inf + 3 at which point the Inf's operator overloading kicks in and returns Inf. VARIATIONS ON THE THEME I list here some variations on the ideas discussed above, because some of these ideas seem good but don't quite fit into the scheme described. Perhaps you can shoehorn them in. I'm still a little hesitant about this "offending object" concept. Perhaps instead, each class could get an array of all the objects and operators in the expression being evaluated. Again, that doesn't necessarily mean an entire mathematical expression, it would just be the subset be evaluated. So, for example, the div by zero handler would get references to the objects containing [1, '/', 0] in this expression: (1/0) + 3 The Exception could have a static method called TriggerException that is a step above actually creating and throwing an exception. TriggerException does what the interpretor does: detect if the ExceptionCreator has a method for a given class of Exception. If TriggerException returns false, then the module should just throw an exception as usual. By using TriggerException, a module can allow users to handle their exceptions in the manner described above. For example, suppose a DBI class used TriggerException to indicate that a database connection has died. The default behavior would be to throw a DbDied exception and exit the block. However, a corporate programmer could set up a special handler that tries to reconnect to that or another database, and if it finds an alternate connection, reaches into the calling block (constructors could get hooks into the calling block), changes the variable that holds the database connection, and calls a DO_OVER. In this way the exception handler becomes more than just a way to report errors: it gets actively involved in fixing them. ExceptionCreator also becomes a more extensible concept, allowing any type of Exception to be handled dynamically. -miko -------------------------------------------------------------------- mail2web - Check your email from the web at http://mail2web.com/ .