diff options
author | elioat <elioat@tilde.institute> | 2023-08-23 07:52:19 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2023-08-23 07:52:19 -0400 |
commit | 562a9a52d599d9a05f871404050968a5fd282640 (patch) | |
tree | 7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/v2ch3/exit.html | |
parent | 5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff) | |
download | tour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz |
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/v2ch3/exit.html')
-rw-r--r-- | js/games/nluqo.github.io/~bh/v2ch3/exit.html | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/v2ch3/exit.html b/js/games/nluqo.github.io/~bh/v2ch3/exit.html new file mode 100644 index 0000000..274e65e --- /dev/null +++ b/js/games/nluqo.github.io/~bh/v2ch3/exit.html @@ -0,0 +1,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 "true] +print [Sorry, that's wrong.] +output "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 "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 "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 "correct +</PRE> + +<P><CODE>Throw</CODE> is a command that requires one input, which must be a +word, called a "tag." 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 "catch tag"), 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 "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 "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 "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 "done [repeat 100 [print "hello + if equalp random 5 0 [throw "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 "nonlocal exit" 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 "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 "zero [mul1 :list] +end + +to mul1 :list +if emptyp :list [output 1] +if equalp first :list 0 [(throw "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 "early [mul1 :list] +end + +to mul1 :list +if emptyp :list [output 1] +if not numberp first :list [(throw "early "non-number)] +if equalp first :list 0 [(throw "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 "defensive" 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 "error [output mul1 :list] +output "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 "error +</PRE> + +<P>If this <CODE>throw</CODE> "unwinds" 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 "below" 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 < (1+count :list) [output item :number :list] +output [] +end + +to safe.item2 :number :list +catch "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 "error [make "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 "error [print :nonexistent] +show error +end + +? <U>sample</U> +[11 [nonexistent has no value] sample + [catch "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 "error [make "variable item 7 :list] +if not emptyp error [make "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 "use up" 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 "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 "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 "gamename +print [You can play wumpus, dungeon, or rummy.] +print [Which do you want?] +make "gamename first rl +if :gamename = "wumpus [wumpus] +if :gamename = "dungeon [dungeon] +if :gamename = "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> |