From 42155d759e66e5847f4795f6c883e25db4ac049e Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Thu, 8 Jan 2004 17:04:02 +0000 Subject: [PATCH] More stuff about flow control. Bug fixes in example. --- doc/ref/scheme-control.texi | 146 +++++++++++++++++++++++++++--------- 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/doc/ref/scheme-control.texi b/doc/ref/scheme-control.texi index d56b543f7..36918373d 100644 --- a/doc/ref/scheme-control.texi +++ b/doc/ref/scheme-control.texi @@ -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