Repository navigation
Expand file tree
/
Copy pathexceptions.cm
More file actions
313 lines (258 loc) · 11.6 KB
/
Copy pathexceptions.cm
File metadata and controls
313 lines (258 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
##
When something goes wrong.
An exception is for the case a function cannot answer: not "the answer is
nothing", which is an optional, but "there is no answer and I cannot go on". Telling those
two apart is most of using exceptions well, and the last section is about that.
The rest is short. A program declares an exception by extending one, throws it, and catches
it by naming it or any of its ancestors.
##
# ---- Declaring some ------------------------------------------------------------------
##
@summary: Anything that can go wrong in a kitchen.
@remarks: Extending Exception is all it takes. The constructor passes a message up with
base(...), which is what Message() gives back later — Exception declares nothing else a
program can see, so the message is the one thing every exception carries.
##
model KitchenProblem extends Exception
# @summary: A kitchen problem carrying a message about what went wrong.
# @message: what to tell whoever catches it.
public function KitchenProblem(string message)
base(message);
end function
end model
##
@summary: A kitchen problem where something has run out.
@remarks: Exceptions form a hierarchy like any other model, and the hierarchy is what makes
them useful: a caller who cares which thing went wrong names the exact one, and a caller who
only cares that something did names the parent. Neither has to know about the other.
##
model OutOfIngredient extends KitchenProblem
# @summary: Which ingredient ran out, for a caller that wants to know.
public string Ingredient;
# @summary: Says which ingredient is missing.
# @ingredient: the one that ran out.
public function OutOfIngredient(string ingredient)
base("no " + ingredient + " left");
this.Ingredient = ingredient;
end function
end model
# @summary: A kitchen problem where the oven has stopped working.
model OvenBroken extends KitchenProblem
# @summary: Says the oven will not light.
public function OvenBroken()
base("the oven will not light");
end function
end model
shared model Program
function Main()
Program.ThrowingAndCatching();
Program.CatchingByAncestor();
Program.WhichClauseWins();
Program.FinallyRunsEitherWay();
Program.PassingItOn();
Program.TheOnesTheLanguageRaises();
Program.WhenNotToThrow();
end function
# ---- The shape of it ----------------------------------------------------------------
# @summary: Bakes with an ingredient, and fails in a different way for each.
# @ingredient: what to bake with.
# @throws: OutOfIngredient for flour, OvenBroken for anything electrical.
function Bake(string ingredient)
if ingredient == "flour"
throw new OutOfIngredient("flour");
end if
Console.WriteLine(" baked with {{ingredient}}");
end function
# @summary: Throwing one and catching it by name.
function ThrowingAndCatching()
Console.WriteLine("== throwing and catching ==");
try
Program.Bake("sugar");
Program.Bake("flour");
Console.WriteLine(" never reached");
catch OutOfIngredient problem
Console.WriteLine(" {{problem.Message()}}");
# A declared exception may carry whatever it likes besides the message.
Console.WriteLine(" the missing one was {{problem.Ingredient}}");
end try
end function
# ---- Catching a parent --------------------------------------------------------------
##
A catch takes the type it names and anything descending from it, so one clause handles
a whole family. 'catch Exception' takes every exception a program can cause, declared
or not.
What it does not take is a fault in the language itself. Those are not the program's to
answer for, and a clause that caught one would report a bug in the compiler as something
the program did.
##
function CatchingByAncestor()
Console.WriteLine("== catching a parent ==");
try
throw new OvenBroken();
catch KitchenProblem problem
Console.WriteLine(" something in the kitchen: {{problem.Message()}}");
end try
try
throw new OutOfIngredient("eggs");
catch Exception problem
Console.WriteLine(" something, anything: {{problem.Message()}}");
end try
end function
# ---- Several clauses ------------------------------------------------------------------
##
The first clause that matches wins, so the particular ones go above the general ones.
Written the other way round, a 'catch Exception' at the top would take everything and
nothing below it could ever run.
##
function WhichClauseWins()
Console.WriteLine("== which clause wins ==");
Program.Attempt(new OutOfIngredient("butter"));
Program.Attempt(new OvenBroken());
Program.Attempt(new Exception("something else entirely"));
end function
# @summary: Throws whatever it is handed, so a catch can be watched choosing.
# @what: the exception to raise.
function Attempt(Exception what)
try
throw what;
catch OutOfIngredient problem
Console.WriteLine(" shopping: {{problem.Ingredient}}");
catch KitchenProblem problem
Console.WriteLine(" kitchen: {{problem.Message()}}");
catch Exception problem
Console.WriteLine(" other: {{problem.Message()}}");
end try
end function
# ---- finally ----------------------------------------------------------------------------
##
Runs whichever way the block was left: off the end, through a catch, or on the way out
of a throw nothing here caught. It is where the things that must happen anyway go.
##
function FinallyRunsEitherWay()
Console.WriteLine("== finally ==");
try
Console.WriteLine(" nothing wrong");
finally
Console.WriteLine(" and the kitchen is tidied");
end try
try
throw new OvenBroken();
catch KitchenProblem _
Console.WriteLine(" caught it");
finally
Console.WriteLine(" and the kitchen is tidied again");
end try
end function
# ---- Handing it further up --------------------------------------------------------------
##
A catch that cannot really deal with the problem can say so by throwing again. There is
no bare 'throw': the one that was caught is a value with a name, so it is thrown the way
anything is thrown.
Catching something only to hide it is the mistake this exists to avoid.
##
function PassingItOn()
Console.WriteLine("== handing it on ==");
try
Program.NoteAndRethrow();
catch KitchenProblem problem
Console.WriteLine(" the caller dealt with it: {{problem.Message()}}");
end try
end function
# @summary: Catching one, saying something about it, and letting it carry on.
function NoteAndRethrow()
try
throw new OutOfIngredient("salt");
catch OutOfIngredient problem
Console.WriteLine(" noted, but this is not mine to fix");
throw problem;
end try
end function
# ---- The ones the language raises ---------------------------------------------------------
##
A program does not throw all of these itself; the language raises them when something
goes wrong that it could not see while compiling. Each is caught the same way.
A function never declares what it may throw, and nothing forces a caller to handle it.
That is deliberate: the alternative teaches people to write a catch that does nothing
with what it caught.
##
function TheOnesTheLanguageRaises()
Console.WriteLine("== the ones the language raises ==");
integer[] three = {1, 2, 3};
integer zero = 0;
integer? nothing;
integer count = 5;
# Each of these asks for a value it cannot have. Nothing is ever printed: working the
# argument out is what raises, so the call around it never happens.
Program.Show(function() Console.WriteLine(1 / zero); end function);
Program.Show(function() Console.WriteLine(three[7]); end function);
Program.Show(function() Console.WriteLine(nothing.Value()); end function);
Program.Show(function() Console.WriteLine(2147483647 * 2147483647 * 4); end function);
Program.Show(function() Console.WriteLine(count.Format("Q9")); end function);
end function
##
Runs something and says which exception came out, if any.
The parameter is written 'delegate()' rather than 'Function'. Function is the root
every function type descends from — it says a value is a function without saying what
shape, and a shape is exactly what calling one needs.
##
function Show(delegate() attempt)
try
attempt();
Console.WriteLine(" nothing went wrong");
catch DivideByZeroException _
Console.WriteLine(" divided by zero");
catch IndexOutOfRangeException _
Console.WriteLine(" reached past the end");
catch EmptyOptionalException _
Console.WriteLine(" read an empty optional");
catch OverflowException _
Console.WriteLine(" too big to hold");
catch FormatException _
Console.WriteLine(" a pattern that means nothing");
end try
end function
# ---- When not to throw ------------------------------------------------------------------
##
The question worth asking every time: is this an answer, or the absence of one?
Looking for something and not finding it is an answer, and an optional says it. A
caller then has to deal with the empty case before it can read the value, which is the
checking a catch would have made optional.
Throwing is for the case where there is no answer to give and going on would be worse
than stopping.
##
function WhenNotToThrow()
Console.WriteLine("== an answer, or the absence of one ==");
string[] shelf = {"flour", "sugar"};
# Not finding it is ordinary, so this yields nothing rather than throwing.
Console.WriteLine(" looking for sugar: {{Program.Find(shelf, "sugar").Or("not there")}}");
Console.WriteLine(" looking for salt: {{Program.Find(shelf, "salt").Or("not there")}}");
# Being asked for a negative amount is not an answer at all.
try
Program.Weigh(-1);
catch ArgumentException problem
Console.WriteLine(" {{problem.Message()}}");
end try
end function
# @summary: Looks for something on a shelf, where absence is ordinary.
# @shelf: what is on the shelf.
# @what: the thing to look for.
# @yields: the item, or nothing where the shelf does not hold it.
string? function Find(string[] shelf, string what)
loop each item in shelf
if item == what
yield item;
end if
end loop
string? missing;
yield missing;
end function
# @summary: Weighs something, where an impossible weight is not an ordinary answer.
# @grams: how much it weighs.
# @throws: ArgumentException where the weight is negative.
function Weigh(integer grams)
if grams < 0
throw new ArgumentException("cannot weigh " + grams + " grams");
end if
Console.WriteLine(" weighed {{grams}}g");
end function
end model