mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-01 12:20:26 +02:00
More stuff about flow control. Bug fixes in example.
This commit is contained in:
parent
7ebccde82e
commit
42155d759e
1 changed files with 110 additions and 36 deletions
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue