1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-01 04:10:18 +02:00

More stuff about flow control. Bug fixes in example.

This commit is contained in:
Marius Vollmer 2004-01-08 17:04:02 +00:00
parent 7ebccde82e
commit 42155d759e

View 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