[88e0080] | 1 | Design of Exceptions and EHM in Cforall: |
---|
| 2 | |
---|
| 3 | Currently this is a combination of ideas and big questions that still have to |
---|
| 4 | be addressed. It also includes some other error handling options, how they |
---|
| 5 | interact and compare to exceptions. |
---|
| 6 | |
---|
| 7 | |
---|
| 8 | What is an Exception: |
---|
| 9 | |
---|
| 10 | In other words what do we throw? What is matched against, how does it carry |
---|
| 11 | data with it? A very important question that has not been answered. |
---|
| 12 | |
---|
| 13 | Option 1: Strutures |
---|
| 14 | |
---|
| 15 | Considering the current state of Cforall the most natural form of the |
---|
| 16 | exception would likely be a struture, implementing a trait repersenting the |
---|
| 17 | minimum features of an exception. This has many advantages, including arbitray |
---|
| 18 | fields, some polymorphism and it matches exceptations of many current systems. |
---|
| 19 | |
---|
| 20 | The main issue with this is matching, without OOP inheritance there is no |
---|
| 21 | exception hierarchy. Meaning all handling has to happen on the exact exception |
---|
| 22 | without the ease of grouping parents. There are several ways to attempt to |
---|
| 23 | recover this. |
---|
| 24 | |
---|
| 25 | The first is with conditional matching (a check after the type has been |
---|
| 26 | matched) which allows for matching particular values of a known type. However |
---|
| 27 | this does not dynamically expand and requires an extra step (as opposed to |
---|
| 28 | mearly allowing one). I would not recomend this as the primary method. |
---|
| 29 | |
---|
| 30 | Second is to try and use type casts/conversions to create an implicate |
---|
| 31 | hierachy, so that a catch clause catches anything of the given type or |
---|
| 32 | anything that converts to the given type. |
---|
| 33 | |
---|
| 34 | Plan9 (from what I know of it) would be a powerful tool here. Even with it, |
---|
| 35 | creating a hierarchy of types at runtime might be too expencive. Esecially |
---|
| 36 | since they are less likely to be tree like at that point. |
---|
| 37 | |
---|
| 38 | Option 2: Tags |
---|
| 39 | |
---|
| 40 | The other option is to introduce a new construct into the language. A tag |
---|
| 41 | repersents a type of exception, it is not a structure or variable (or even |
---|
| 42 | a normal type). It may be usable in some of those contexts. |
---|
| 43 | |
---|
| 44 | Tags can declare an existing tag as its parent. Tags can be caught by handlers |
---|
| 45 | that catch their parents. (There is a single base_exception that all other |
---|
| 46 | exceptions are children of eventually.) This allows for grouping of exceptions |
---|
| 47 | that repersent similar errors. |
---|
| 48 | |
---|
| 49 | Tags should also have some assotiated data, where and on what did the error |
---|
| 50 | occur. Keeping with the otherness of exception tags and allowing them to be |
---|
| 51 | expanded, using a parameter list. Each exception can have a list of paramters |
---|
| 52 | given to it on a throw. Each tag would have a declared list of parameters |
---|
| 53 | (which could be treated more like a set of fields as well). Child tags must |
---|
| 54 | use their parent's list as a prefix to their own, so that the parameters can |
---|
| 55 | be accessed when the child tag is matched against the parent. |
---|
| 56 | |
---|
| 57 | Option N: ... |
---|
| 58 | |
---|
| 59 | This list is not complete. |
---|
| 60 | |
---|
| 61 | |
---|
| 62 | Seperating Termination and Resumption: |
---|
| 63 | |
---|
| 64 | Differentating the types of exceptions based on exception would be hard with |
---|
| 65 | exceptions as structures. It is possible with exceptions as tags by having |
---|
| 66 | two base exceptions, one for each type of throw. However recompining them |
---|
| 67 | to dual types would be harder. |
---|
| 68 | |
---|
| 69 | Reguardless, using different keywords would also be useful for clarity, even |
---|
| 70 | if it does not add functality. Using the C++ like keywords would be a good |
---|
| 71 | base. Resumption exceptions could use a different set (ex. raise->handle) or |
---|
| 72 | use resume as a qualifier on the existing statements. |
---|
| 73 | |
---|
| 74 | |
---|
| 75 | Conditional Matching: |
---|
| 76 | |
---|
| 77 | A possible useful feature, it allows for arbitrary checks on a catch block |
---|
| 78 | instead of merely matching a type. However there are few use cases that |
---|
| 79 | cannot be avoided with proper use of a type hierarchy, and this shrinks even |
---|
| 80 | further with a good use of re-throws. |
---|
| 81 | |
---|
| 82 | Also it assumes one sweep, that might also be a problem. But might also give |
---|
| 83 | it an advantage over re-throws. |
---|
| 84 | |
---|
| 85 | |
---|
| 86 | Alternatives: Implicate Handlers & Return Unions |
---|
| 87 | |
---|
| 88 | Both act as a kind of local version of an exception. Implicate handlers act as |
---|
| 89 | resumption exceptions and return unions like termination exceptions. By local |
---|
| 90 | I mean they work well at one or two levels of calls, but do not cover N levels |
---|
| 91 | as cleanly. |
---|
| 92 | |
---|
| 93 | Implicate handles require a growing number of function pointers (which should |
---|
| 94 | not be used very often) to be passed to functions, creating and additional |
---|
| 95 | preformance cost. Return unions have to be checked and handled at every level, |
---|
| 96 | which has some preformance cost, but also can significantly clutter code. |
---|
| 97 | Proper tools can help with the latter. |
---|
| 98 | |
---|
| 99 | However, they may work better at that local level as they do not require stack |
---|
| 100 | walking or unwinding. In addition they are closer to regular control flow and |
---|
| 101 | are easier to staticly check. So even if they can't replace exceptions |
---|
| 102 | (directly) they may still be worth using together. |
---|
| 103 | |
---|
| 104 | For instance, consider the Python iterator interface. It uses a single |
---|
| 105 | function, __next__, to access the next value and to signal the end of the |
---|
| 106 | sequence. If a value is returned, it is the next value, if the StopIteration |
---|
| 107 | exception is thrown the sequence has finished. |
---|
| 108 | |
---|
| 109 | However almost every use of an iterator will add a try-except block around the |
---|
| 110 | call site (possibly through for or next) to catch and handle the exception |
---|
| 111 | immediately, ignoring the advantages of more distant exception handling. |
---|
| 112 | |
---|
| 113 | In this case it may be cleaner to use a Maybe for both cases (as in Rust) |
---|
| 114 | which gives similar results without having to jump to the exception handler. |
---|
| 115 | This will likely handle the error case more efficiently and the success case a |
---|
| 116 | bit less so. |
---|
| 117 | |
---|
| 118 | It also mixes the error and regular control flow, which can hurt readablity, |
---|
| 119 | but very little if the handling is simple, for instance providing a default |
---|
| 120 | value. Similarly, if the error (or alternate outcome) is common enough |
---|
| 121 | encoding it in the function signature may be good commuication. |
---|
| 122 | |
---|
| 123 | In short, these errors seem to be more effective when errors are likely and |
---|
| 124 | immediate. High failure operations, especially ones with failures that can |
---|
| 125 | be handled locally, might be better off using these instead of exceptions. |
---|
| 126 | |
---|
| 127 | Also the implicate handlers and return unions could use exceptions as well. |
---|
| 128 | For instance, a useful default might handler might be to throw an exception, |
---|
| 129 | seaching up the stack for a solution if one is not locally provided. |
---|
| 130 | |
---|
| 131 | Or here is a possible helper for unpacking a Result value: |
---|
| 132 | forall(otype T, otype E | exception(E)) |
---|
| 133 | T get_or_throw (Result(T, E) * this) { |
---|
| 134 | if (is_success(this)) { |
---|
| 135 | return get_success(this); |
---|
| 136 | } else { |
---|
| 137 | throw get_failure(this); |
---|
| 138 | } |
---|
| 139 | } |
---|
| 140 | So they can feed off of each other. |
---|