about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/v2ch3/exit.html
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
committerelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
commit562a9a52d599d9a05f871404050968a5fd282640 (patch)
tree7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/v2ch3/exit.html
parent5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff)
downloadtour-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.html487
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 &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>