More stuff about flow control. Bug fixes in example.
authorMarius Vollmer <mvo@zagadka.de>
Thu, 8 Jan 2004 17:04:02 +0000 (17:04 +0000)
committerMarius Vollmer <mvo@zagadka.de>
Thu, 8 Jan 2004 17:04:02 +0000 (17:04 +0000)
doc/ref/scheme-control.texi

index d56b543..3691837 100644 (file)
@@ -2,6 +2,20 @@
 @node Control Mechanisms
 @chapter Controlling the Flow of Program Execution
 
+@menu
+* begin::                       Evaluating a sequence of expressions.
+* if cond case::                Simple conditional evaluation.
+* and or::                      Conditional evaluation of a sequence.
+* while do::                    Iteration mechanisms.
+* Continuations::               Continuations.
+* Multiple Values::             Returning and accepting multiple values.
+* Exceptions::                  Throwing and catching exceptions.
+* Error Reporting::             Procedures for signaling errors.
+* Dynamic Wind::                Guarding against non-local entrance/exit.
+* Frames::                      Another way to handle non-localness
+* Handling Errors::             How to handle errors in C code.
+@end menu
+
 Scheme has a more general view of program flow than C, both locally and
 non-locally.
 
@@ -11,7 +25,69 @@ refers to situations where the program jumps across one or more levels
 of function activations without using the normal call or return
 operations.
 
-[ XXX - tail calls instead of goto. ]
+The primitive means of C for local control flow is the @code{goto}
+statement, together with @code{if}.  Loops done with @code{for},
+@code{while} or @code{do} could in principle be rewritten with just
+@code{goto} and @code{if}.  In Scheme, the primitive means for local
+control flow is the @emph{function call} (together with @code{if}).
+Thus, the repetition of some computation in a loop is ultimately
+implemented by a function that calls itself, that is, by recursion.
+
+This approach is theoretically very powerful since it is easier to
+reason formally about recursion than about gotos.  In C, using
+recursion exclusively would not be practical, tho, since it would eat
+up the stack very quickly.  In Scheme, however, it is practical:
+function calls that appear in a @dfn{tail position} do not use any
+additional stack space.
+
+A function call is in a tail position when it is the last thing the
+calling function does.  The value returned by the called function is
+immediately returned from the calling function.  In the following
+example, the call to @code{bar-1} is in a tail position, while the
+call to @code{bar-2} is not.  (The call to @code{1-} in @code{foo-2}
+is in a tail position, tho.)
+
+@lisp
+(define (foo-1 x)
+  (bar-1 (1- x)))
+
+(define (foo-2 x)
+  (1- (bar-2 x)))
+@end lisp
+
+Thus, when you take care to recurse only in tail positions, the
+recursion will only use constant stack space and will be as good as a
+loop constructed from gotos.
+
+Scheme offers a few syntactic abstractions (@code{do} and @dfn{named}
+@code{let}) that make writing loops slightly easier.
+
+But only Scheme functions can call other functions in a tail position:
+C functions can not.  This matters when you have, say, two functions
+that call each other recursively to form a common loop.  The following
+(unrealistic) example shows how one might go about determing whether a
+non-negative integer @var{n} is even or odd.
+
+@lisp
+(define (my-even? n)
+  (cond ((zero? n) #t)
+        (else (my-odd? (1- n)))))
+
+(define (my-odd? n)
+  (cond ((zero? n) #f)
+        (else (my-even? (1- n)))))
+@end lisp
+
+Because the calls to @code{my-even?} and @code{my-odd?} are in tail
+positions, these two procedures can be applied to arbitrary large
+integers without overflowing the stack.  (They will still take a lot
+of time, of course.)
+
+However, when one or both of the two procedures would be rewritten in
+C, it could no longer call its companion in a tail position (since C
+does not have this concept).  You might need to take this
+consideration into account when deciding which parts of your program
+to write in Scheme and which in C.
 
 In addition to calling functions and returning from them, a Scheme
 program can also exit non-locally from a function so that the control
@@ -28,16 +104,17 @@ In general, these non-local jumps are done by invoking
 @code{call-with-current-continuation}.  Guile also offers a slightly
 restricted set of functions, @code{catch} and @code{throw}, that can
 only be used for non-local exits.  This restriction makes them more
-efficient.  Error reporting (with the function @code{error}) is done by
-invoking @code{throw}, for example.  The functions @code{catch} and
-@code{throw} belong to the topic of @dfn{exceptions}.
+efficient.  Error reporting (with the function @code{error}) is
+implemented by invoking @code{throw}, for example.  The functions
+@code{catch} and @code{throw} belong to the topic of @dfn{exceptions}.
 
 Since Scheme functions can call C functions and vice versa, C code can
-experience the more general flow of control of Scheme as well.  It is
+experience the more general control flow of Scheme as well.  It is
 possible that a C function will not return at all, or will return more
 than once.  While C does offer @code{setjmp} and @code{longjmp} for
-non-local exits, it is still a unusual thing for C code.  In contrast,
-non-local exits are very common in Scheme, mostly to report errors.
+non-local exits, it is still an unusual thing for C code.  In
+contrast, non-local exits are very common in Scheme, mostly to report
+errors.
 
 You need to be prepared for the non-local jumps in the control flow
 whenever you use a function from @code{libguile}: it is best to assume
@@ -54,24 +131,11 @@ its previous value when @code{with-output-to-port} returns normally or
 when it is exited non-locally.  Likewise, the port needs to be set again
 when control enters non-locally.
 
-Scheme code can use the @code{dynamic-wind} function to arrange the
-setting and resetting of the global state.  C code could use the
+Scheme code can use the @code{dynamic-wind} function to arrange for
+the setting and resetting of the global state.  C code could use the
 corresponding @code{scm_internal_dynamic_wind} function, but it might
-prefer to use the @dfn{frames} concept that is more natural for C code.
-
-@menu
-* begin::                       Evaluating a sequence of expressions.
-* if cond case::                Simple conditional evaluation.
-* and or::                      Conditional evaluation of a sequence.
-* while do::                    Iteration mechanisms.
-* Continuations::               Continuations.
-* Multiple Values::             Returning and accepting multiple values.
-* Exceptions::                  Throwing and catching exceptions.
-* Error Reporting::             Procedures for signaling errors.
-* Dynamic Wind::                Guarding against non-local entrance/exit.
-* Frames::                      Another way to handle non-localness
-* Handling Errors::             How to handle errors in C code.
-@end menu
+prefer to use the @dfn{frames} concept that is more natural for C
+code.
 
 
 @node begin
@@ -81,12 +145,12 @@ prefer to use the @dfn{frames} concept that is more natural for C code.
 @cindex sequencing
 @cindex expression sequencing
 
-@code{begin} is used for grouping several expressions together so that
-they syntactically are treated as if they were one expression.  This is
-particularly important when syntactic expressions are used which only
-allow one expression, but the programmer wants to use more than one
-expression in that place.  As an example, consider the conditional
-expression below:
+The @code{begin} syntax is used for grouping several expressions
+together so that they are treated as if they were one expression.
+This is particularly important when syntactic expressions are used
+which only allow one expression, but the programmer wants to use more
+than one expression in that place.  As an example, consider the
+conditional expression below:
 
 @lisp
 (if (> x 0)
@@ -1103,10 +1167,10 @@ scm_foo (SCM s1, SCM s2)
   scm_frame_begin (0);
 
   c_s1 = scm_to_string (s1);
-  scm_frame_unwind (free, c_s1, SCM_F_EXPLICIT);
+  scm_frame_unwind (free, c_s1, SCM_F_WIND_EXPLICITLY);
 
   c_s2 = scm_to_string (s2);
-  scm_frame_unwind (free, c_s2, SCM_F_EXPLICIT);
+  scm_frame_unwind (free, c_s2, SCM_F_WIND_EXPLICITLY);
 
   c_res = foo (c_s1, c_s2);
   if (c_res == NULL)
@@ -1140,10 +1204,20 @@ For normal frames, use 0.  This will result in a frame that can not be
 reentered with a captured continuation.  When you are prepared to handle
 reentries, include @code{SCM_F_FRAME_REWINDABLE} in @var{flags}.
 
+Being prepared for reentry means that the effects of unwind handlers
+can be undone on reentry.  In the example above, we want to prevent a
+memory leak on non-local exit and thus register an unwind handler that
+frees the memory.  But once the memory is freed, we can not get it
+back on reentry.  Thus reentry can not be allowed.
+
+The consequence is that continuations become less useful when
+non-reenterable frames are captured, but you don't need to worry about
+that too much.
+
 The frame is ended either implicitly when a non-local exit happens, or
 explicitly with @code{scm_end_frame}.  You must make sure that a frame
-is indeed ended properly.  If you fail to call @code{scm_end_frame} each
-@code{scm_begin_frame}, the behavior is undefined.
+is indeed ended properly.  If you fail to call @code{scm_end_frame}
+for each @code{scm_begin_frame}, the behavior is undefined.
 @end deftypefn
 
 @deftypefn {C Function} void scm_frame_end ()
@@ -1170,7 +1244,7 @@ when the current frame ends implicitly.  If @var{flags} contains
 ends explicitly with @code{scm_frame_end}.
 
 The function @code{scm_frame_unwind_with_scm} takes care that @var{data}
-is protected from garbage collected.
+is protected from garbage collection.
 @end deftypefn
 
 @deftypefn {C Function} void scm_frame_rewind (void (*func)(void *), void *data, scm_t_wind_flags flags)
@@ -1181,7 +1255,7 @@ contains @code{SCM_F_WIND_EXPLICITLY}, @var{func} is called immediately
 as well.
 
 The function @code{scm_frame_rewind_with_scm} takes care that @var{data}
-is protected from garbage collected.
+is protected from garbage collection.
 @end deftypefn