[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.
|
---|