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