about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/v2ch3/exit.html
blob: 274e65e10bc94301bbf4b7382504513d8f19f0b4 (plain) (blame)
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
<HTML>
<HEAD>
<TITLE>Computer Science Logo Style vol 2 ch 3: Nonlocal Exit</TITLE>
</HEAD>
<BODY>
<CITE>Computer Science Logo Style</CITE> volume 2:
<CITE>Advanced Techniques</CITE> 2/e Copyright (C) 1997 MIT
<H1>Nonlocal Exit</H1>

<TABLE width="100%"><TR><TD>
<IMG SRC="../csls2.jpg" ALT="cover photo">
<TD><TABLE>
<TR><TD align="right"><CITE><A HREF="http://www.cs.berkeley.edu/~bh/">Brian
Harvey</A><BR>University of California, Berkeley</CITE>
<TR><TD align="right"><BR>
<TR><TD align="right"><A HREF="../pdf/v2ch03.pdf">Download PDF version</A>
<TR><TD align="right"><A HREF="../v2-toc2.html">Back to Table of Contents</A>
<TR><TD align="right"><A HREF="../v2ch2/v2ch2.html"><STRONG>BACK</STRONG></A>
chapter thread <A HREF="../v2ch4/v2ch4.html"><STRONG>NEXT</STRONG></A>
<TR><TD align="right"><A HREF="https://mitpress.mit.edu/books/computer-science-logo-style-second-edition-volume-2">MIT
Press web page for <CITE>Computer Science Logo Style</CITE></A>
</TABLE></TABLE>

<HR>


<P>


<P>This chapter is about the commands <CODE>catch</CODE> and <CODE>throw</CODE>.  These
commands work together as a kind of super-<CODE>stop</CODE> command, which
you can use to stop several levels of procedure invocation at once.

<P><H2>Quiz Program Revisited</H2>

<P>In Chapter 4 of the first volume, which was about predicates, I posed the
problem of a quiz program that would allow three tries to answer each
question.  Here is the method I used then:

<P><PRE>to ask.thrice :question :answer
repeat 3 [if ask.once :question :answer [stop]]
print sentence [The answer is] :answer
end

to ask.once :question :answer
print :question
if equalp readlist :answer [print [Right!] output &quot;true]
print [Sorry, that's wrong.]
output &quot;false
end
</PRE>

<P>I remarked that <CODE>ask.once</CODE> acts like a command, in that it has an
effect (printing stuff), but it's also an operation, which outputs
<CODE>true</CODE> or <CODE>false</CODE>.  What it <EM>really</EM> wants to do is not
output a value but instead be able to stop not only itself but also
its calling procedure <CODE>ask.thrice</CODE>.  Here is another version that
allows just that:

<P><PRE>to qa :question :answer
catch &quot;correct [ask.thrice :question :answer]
end

to ask.thrice :question :answer
repeat 3 [ask.once :question :answer]
print sentence [The answer is] :answer
end

to ask.once :question :answer
print :question
if equalp readlist :answer [print [Right!] throw &quot;correct]
print [Sorry, that's wrong.]
end
</PRE>

<P>To understand this group of procedures, start with <CODE>
ask.thrice</CODE> and suppose the player keeps getting the wrong answer.
Both <CODE>ask.once</CODE> and <CODE>ask.thrice</CODE> are straightforward commands;
the <CODE>repeat</CODE> instruction in <CODE>ask.thrice</CODE> is simpler than it
was in the other version.

<P>Now what if the person answers correctly?  <CODE>Ask.once</CODE> then
evaluates the instruction

<P><PRE>throw &quot;correct
</PRE>

<P><CODE>Throw</CODE> is a command that requires one input, which must be a
word, called a &quot;tag.&quot; The effect of <CODE>throw</CODE> is to stop the current
procedure, like <CODE>stop</CODE>, and to keep stopping higher-level procedures
until it reaches an active <CODE>catch</CODE> whose first input is the same as the
input to <CODE>throw</CODE>.

<P>If that sounds confusing, don't give up; it's because I haven't
explained <CODE>catch</CODE> and you have to understand them together.  The
description of <CODE>catch</CODE> is deceptively simple:  <CODE>Catch</CODE> is a
command that requires two inputs.  The first must be a word (called
the &quot;catch tag&quot;), the
second a list of Logo instructions.  The effect of <CODE>catch</CODE> is the
same as that of <CODE>run</CODE>--it evaluates the instructions in the
list.  <CODE>Catch</CODE> pays no attention to its first input.  That input
is there only for the benefit of <CODE>throw</CODE>.

<P>In this example program <CODE>qa</CODE> invokes <CODE>catch</CODE>; <CODE>catch</CODE> invokes
<CODE>ask.thrice</CODE>, which invokes <CODE>repeat</CODE>, which invokes <CODE>ask.once</CODE>.
To understand how <CODE>throw</CODE> works, you have to remember that primitive
procedures are just as much procedures as user-defined ones.  That's
something we're sometimes lax about.  A couple of paragraphs ago, I said
that <CODE>ask.once</CODE> evaluates the instruction

<P><PRE>throw &quot;correct
</PRE>

<P>if the player answers correctly.  That wasn't really true.
The truth is that <CODE>ask.once</CODE> evaluates the instruction

<P><PRE>if equalp readlist :answer [print [Right!] throw &quot;correct]
</PRE>

<P>by invoking <CODE>if</CODE>.  It is the procedure <CODE>if</CODE> that
actually evaluates the instruction that invokes <CODE>throw</CODE>.  I made
a bit of a fuss about this fine point when we first met <CODE>if</CODE>, but
I've been looser about it since then.  Now, though, we need to go back
to thinking precisely.  The point is that there is a <CODE>catch</CODE>
procedure in the collection of active procedures (<CODE>qa</CODE>, <CODE>catch</CODE>,
<CODE>ask.thrice</CODE>, and so on) at the time <CODE>throw</CODE> is invoked.

<P>(In Chapter 9 of the first volume, I made the point that primitives count as
active procedures and that <CODE>stop</CODE> stops the lowest-level invocation of a
user-defined procedure.  I said that it would be silly for <CODE>stop</CODE> to
stop only the <CODE>if</CODE> that invoked it, but that you could imagine <CODE>stop</CODE>
stopping a <CODE>repeat</CODE>.  I gave

<P><PRE>repeat 100 [print &quot;hello if equalp random 5 0 [stop]]
</PRE>

<P>as an example of something that doesn't work.  But we can
make it work this way:

<P><PRE>catch &quot;done [repeat 100 [print &quot;hello
                         if equalp random 5 0 [throw &quot;done]]]
</PRE>

<P>The <CODE>throw</CODE> stops the <CODE>if</CODE>, the <CODE>repeat</CODE>, and the <CODE>
catch</CODE>.  Here's a little quiz for you: Why don't I say that the <CODE>
throw</CODE> stops the <CODE>equalp</CODE>?)

<P><H2>Nonlocal Exit and Modularity</H2>

<P><CODE>Throw</CODE> is called a &quot;nonlocal exit&quot; because it stops not only
the (user-defined) procedure in which it is used but also possibly
some number of superprocedures of that one.  Therefore, it has an
effect on the program as a whole that's analogous to the effect of
changing the value of a variable that is not local to the procedure
doing the changing.  If you see a <CODE>make</CODE> command used in some
procedure, and the variable whose name is the first input isn't local
to the same procedure, it becomes much harder to understand what that
procedure is really doing.  You can't just read that procedure in
isolation; you have to think about all its superprocedures too.
That's why I've been discouraging you from using global variables.

<P><CODE>Throw</CODE> is an offense against modularity in the same way.  If I
gave you <CODE>ask.once</CODE> to read, without having shown you the rest of
the program, you'd have trouble understanding it.  The point may not
seem so important when you're reading the small example programs in
this book, but when you are working on large projects, with 30 or 300
procedures in them, it becomes much more important.

<P>If I were going to use <CODE>catch</CODE> and <CODE>throw</CODE> in this quiz
project, one thing I might do is rename <CODE>ask.thrice</CODE> and <CODE>
ask.once</CODE> as <CODE>qa1</CODE> and <CODE>qa2</CODE>.  These names would make it clear
that the three procedures are meant to work together and indicate which is a
subprocedure of which.  That name change would help a reader of the
program.  (Remember that <CODE>qa</CODE> and its friends are not the whole project;
they're all subprocedures of a higher-level <CODE>quiz</CODE> procedure.  So
grouping them with similar names really does distinguish them from
something else.)

<P><H2>Nonlocal Output</H2>

<P>Consider this procedure that takes a list of numbers as its input and
computes the product of all the numbers:

<P><PRE>to multiply :list
if emptyp :list [output 1]
output (first :list) * (multiply butfirst :list)
end
</PRE>

<P>Suppose that we intend to use this procedure with very large lists
of numbers, and we have reason to believe that many of the lists will include
a zero element.  If any number in the list is zero, then the product of the
entire list must be zero; we can save time by giving an output of zero as
soon as we discover this:

<P><PRE>to multiply :list
if emptyp :list [output 1]
if equalp first :list 0 [output 0]
output (first :list) * (multiply butfirst :list)
end
</PRE>

<P>This is an improvement, but not enough of one.  To see why, look at this
trace of a typical invocation:

<P><PRE>? <U>trace &quot;multiply</U>
? <U>print multiply [4 5 6 0 1 2 3]</U>
<SMALL><CODE>( multiply [4 5 6 0 1 2 3] )
 ( multiply [5 6 0 1 2 3] )
  ( multiply [6 0 1 2 3] )
   ( multiply [0 1 2 3] )
   multiply outputs 0
  multiply outputs 0
 multiply outputs 0
multiply outputs 0
</CODE></SMALL>0
</PRE>

<P>Each of the last three lines indicates an invocation of
<CODE>multiply</CODE> in which the zero output by a lower level is multiplied
by a number seen earlier in the list: first 6, then 5, then 4.  It
would be even better to avoid those extra multiplications:

<P><PRE>to multiply :list
output catch &quot;zero [mul1 :list]
end

to mul1 :list
if emptyp :list [output 1]
if equalp first :list 0 [(throw &quot;zero 0)]
output (first :list) * (mul1 butfirst :list)
end

? <U>trace [multiply mul1]</U>
? <U>print multiply [4 5 6 0 1 2 3]</U>
<SMALL><CODE>( multiply [4 5 6 0 1 2 3] )
 ( mul1 [4 5 6 0 1 2 3] )
  ( mul1 [5 6 0 1 2 3] )
   ( mul1 [6 0 1 2 3] )
    ( mul1 [0 1 2 3] )
multiply outputs 0
</CODE></SMALL>0
</PRE>

<P>This time, as soon as <CODE>mul1</CODE> sees a zero in the list, it
arranges for an immediate return to <CODE>multiply</CODE>, without completing
the other three pending invocations of <CODE>mul1</CODE>.

<P>In the definition of <CODE>mul1</CODE>, the parentheses around the invocation
of <CODE>throw</CODE> are required, because in this situation we are giving <CODE>
throw</CODE> an optional second input.  When given a second input, <CODE>throw</CODE>
acts as a super-<CODE>output</CODE> instead of as a super-<CODE>stop</CODE>.  That is,
<CODE>throw</CODE> finds the nearest enclosing matching <CODE>catch</CODE>, as usual,
but arranges that that matching <CODE>catch</CODE> outputs a value, namely the
second input to <CODE>throw</CODE>.  In this example, the word <CODE>zero</CODE> is the
catch tag, and the number <CODE>0</CODE> is the output value.

<P>The same trick that I've used here for efficiency reasons can also be used
to protect against the possibility of invalid input data.  This time, suppose
that we want to multiply a list of numbers, but we suspect that occasionally
the user of the program might accidentally supply an input list that includes
a non-numeric member.  A small modification will prevent a Logo error
message:

<P><PRE>to multiply :list
output catch &quot;early [mul1 :list]
end

to mul1 :list
if emptyp :list [output 1]
if not numberp first :list [(throw &quot;early &quot;non-number)]
if equalp first :list 0 [(throw &quot;early 0)]
output (first :list) * (mul1 butfirst :list)
end

? <U>print multiply [781 105 87 foo 24 13 6]</U>
non-number
</PRE>

<P>I've changed the catch tag, even though Logo wouldn't care,
because using the word <CODE>zero</CODE> as the tag is misleading now that it
also serves the purpose of catching non-numeric data.

<P>

<H2>Catching Errors</H2>

<P>On the other hand, if we don't expect to see invalid data very often,
then checking every list member to make sure it's a number is needlessly
time-consuming; also, this &quot;defensive&quot; test makes the program structure
more complicated and therefore harder for people to read.  Instead, I'd
like to be able to multiply the list members, and let Logo worry about
possible non-numeric input.  Here's how:

<P><PRE>to multiply :list
catch &quot;error [output mul1 :list]
output &quot;non-number
end

to mul1 :list
if emptyp :list [output 1]
output (first :list) * (mul1 butfirst :list)
end

? <U>print multiply [3 4 5]</U>
60
? <U>print multiply [3 four 5]</U>
non-number
</PRE>

<P>To understand how this works, you must know what Logo does when some
primitive procedure (such as <CODE>*</CODE> in this example) complains about
an error.  The Logo error handler automatically carries out the
instruction

<P><PRE>throw &quot;error
</PRE>

<P>If this <CODE>throw</CODE> &quot;unwinds&quot; the active procedures all
the way to top level without finding a corresponding <CODE>catch</CODE>, then
Logo prints the error message.  If you do catch the error, no message
is printed.

<P>If you are paused (see Chapter 15 of the first volume), the situation is a
little more complicated.  Imagine that there is a procedure called <CODE>
pause.loop</CODE> that reads and evaluates the instructions you type while
paused.  The implicit <CODE>throw</CODE> on an error can be caught by a <CODE>catch</CODE>
that is invoked &quot;below&quot; that interactive level.  That is, during the pause
you can invoke a procedure that catches errors.  But if you don't do that,
<CODE>pause.loop</CODE> will catch the error and print the appropriate message.
(You understand, I hope, that this is an imaginary procedure.  I've just
given it a name to make the point that the interactive instruction evaluator
that is operating during a pause is midway through the collection of active
procedures starting with the top-level one and ending with the one that
caused the error.)  What all this means, more loosely, is that an error
during a pause can't get you all the way back to top level, but only to where
you were paused.

<P>You should beware of the fact that stopping a program by typing control-C
or command-period, depending on the type of computer you're using, is
handled as if it were an error.  That is, it can be caught.  So if you
write a program that catches errors and never stops, you're in
trouble.  You may have to turn the computer off and start over again
to escape!

<P>If you use the <CODE>item</CODE> primitive to ask for more
items than are in the list, it's an error.  Here are two versions of
<CODE>item</CODE> that output the empty list instead:

<P><PRE>to safe.item1 :number :list
if :number &lt; (1+count :list) [output item :number :list]
output []
end

to safe.item2 :number :list
catch &quot;error [output item :number :list]
output []
end
</PRE>

<P>The first version explicitly checks, before invoking <CODE>
item</CODE>, to make sure the item number is small enough.  The second
version goes ahead and invokes <CODE>item</CODE> without checking, but it
arranges to catch any error that happens.  If there is no error, the
<CODE>output</CODE> ends the running of the procedure.  If we get to the next
instruction line, we know there must have been an error.  The second
version of the procedure is a bit faster because it doesn't have to
do all that arithmetic before trying <CODE>item</CODE>.  Also, the first version
only tests for one possible error; it will still bomb out, for example,
if given a negative item number.  The second version is safe against
<EM>any</EM> bad input.

<P>This technique works well if the instruction list <CODE>output</CODE>s or
<CODE>stop</CODE>s.  But what if we want to do something like

<P><PRE>catch &quot;error [make &quot;variable item 7 :list]
</PRE>

<P>and we want to put something special in the variable if
there is an error?  In this example, the procedure will continue to
its next instruction whether or not an error was caught.  We need a
way to ask Logo about any error that might have happened.
For this purpose we use the operation <CODE>error</CODE>.
This operation takes no inputs.  It outputs a list with information
about the most recently caught error.  If no error has been caught, it
outputs the empty list.  Otherwise it outputs a list of four members: a
numeric error code, the text of the error message that would otherwise
have been printed, the name of the procedure in which the error happened,
and the instruction line that was being evaluated.

<P><PRE>to sample
catch &quot;error [print :nonexistent]
show error
end

? <U>sample</U>
[11 [nonexistent has no value] sample
    [catch &quot;error [print :nonexistent]]]
</PRE>

<P>But for now all
that matters is that the output will be nonempty if an error was
caught.  So I can say

<P><PRE>catch &quot;error [make &quot;variable item 7 :list]
if not emptyp error [make &quot;variable []]
</PRE>

<P>This will put an empty list into the variable if there is an
error in the first line.

<P>You can only invoke <CODE>error</CODE> once for each caught error.  If you
invoke <CODE>error</CODE> a second time, it will output the empty list.
That's so that you don't get confused by trying to catch an error
twice and having an error actually happen the first time but not the
second time.  If you'll need to refer to the contents of the <CODE>
error</CODE> list more than once, put it in a variable.

<P>Just in case you've previously caught an error without invoking <CODE>error</CODE>,
it's a good idea to use the instruction

<P><PRE>ignore error
</PRE>

<P>before catching an error and invoking <CODE>error</CODE> to test
whether or not the error occurred.  <CODE>Ignore</CODE> is a Berkeley Logo
primitive that takes one input and does nothing with it; the sole
purpose of the instruction is to &quot;use up&quot; any earlier caught error
so that the next invocation of <CODE>error</CODE> will return an empty list
if no error is caught this time.

<P><H2>Ending It All</H2>

<P>You can stop all active procedures and return to top level by
evaluating the instruction

<P><PRE>throw &quot;toplevel
</PRE>

<P>This is a special kind of <CODE>throw</CODE> that can't be caught.

<P>You've seen this instruction before, in the first volume, where I mentioned
it as a way to get out of a pause.  That's where it's most useful.
Before you use it in a procedure, though, you should be sure that you
<EM>really</EM> want to stop everything.  For example, suppose you're
writing a game program.  If the player gets zapped by an evil Whatzit,
he's dead and the game is over.  So you write

<P><PRE>to zap.player
print [You're dead!]
throw &quot;toplevel
end
</PRE>

<P>because <CODE>zap.player</CODE> might be invoked several levels
deep, but you want to stop everything.  But one day you decide to take
three different games you've written and combine them into a single
program:

<P><PRE>to play
local &quot;gamename
print [You can play wumpus, dungeon, or rummy.]
print [Which do you want?]
make &quot;gamename first rl
if :gamename = &quot;wumpus [wumpus]
if :gamename = &quot;dungeon [dungeon]
if :gamename = &quot;rummy [rummy]
if not memberp :gamename [wumpus dungeon rummy] [print [No such game!]]
play
end
</PRE>

<P>Now your game is no longer the top-level procedure.  <CODE>
play</CODE> wants to keep going after a game is over.  By throwing to
toplevel in the game program, you make that impossible.

<P><A HREF="../v2-toc2.html">(back to Table of Contents)</A>
<P><A HREF="../v2ch2/v2ch2.html"><STRONG>BACK</STRONG></A>
chapter thread <A HREF="../v2ch4/v2ch4.html"><STRONG>NEXT</STRONG></A>

<P>
<ADDRESS>
<A HREF="../index.html">Brian Harvey</A>, 
<CODE>bh@cs.berkeley.edu</CODE>
</ADDRESS>
</BODY>
</HTML>