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
|
@node Control Mechanisms
|
||||||
@chapter Controlling the Flow of Program Execution
|
@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
|
Scheme has a more general view of program flow than C, both locally and
|
||||||
non-locally.
|
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
|
of function activations without using the normal call or return
|
||||||
operations.
|
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
|
In addition to calling functions and returning from them, a Scheme
|
||||||
program can also exit non-locally from a function so that the control
|
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
|
@code{call-with-current-continuation}. Guile also offers a slightly
|
||||||
restricted set of functions, @code{catch} and @code{throw}, that can
|
restricted set of functions, @code{catch} and @code{throw}, that can
|
||||||
only be used for non-local exits. This restriction makes them more
|
only be used for non-local exits. This restriction makes them more
|
||||||
efficient. Error reporting (with the function @code{error}) is done by
|
efficient. Error reporting (with the function @code{error}) is
|
||||||
invoking @code{throw}, for example. The functions @code{catch} and
|
implemented by invoking @code{throw}, for example. The functions
|
||||||
@code{throw} belong to the topic of @dfn{exceptions}.
|
@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
|
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
|
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
|
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, it is still an unusual thing for C code. In
|
||||||
non-local exits are very common in Scheme, mostly to report errors.
|
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
|
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
|
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 it is exited non-locally. Likewise, the port needs to be set again
|
||||||
when control enters non-locally.
|
when control enters non-locally.
|
||||||
|
|
||||||
Scheme code can use the @code{dynamic-wind} function to arrange the
|
Scheme code can use the @code{dynamic-wind} function to arrange for
|
||||||
setting and resetting of the global state. C code could use the
|
the setting and resetting of the global state. C code could use the
|
||||||
corresponding @code{scm_internal_dynamic_wind} function, but it might
|
corresponding @code{scm_internal_dynamic_wind} function, but it might
|
||||||
prefer to use the @dfn{frames} concept that is more natural for C code.
|
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
|
|
||||||
|
|
||||||
|
|
||||||
@node begin
|
@node begin
|
||||||
|
@ -81,12 +145,12 @@ prefer to use the @dfn{frames} concept that is more natural for C code.
|
||||||
@cindex sequencing
|
@cindex sequencing
|
||||||
@cindex expression sequencing
|
@cindex expression sequencing
|
||||||
|
|
||||||
@code{begin} is used for grouping several expressions together so that
|
The @code{begin} syntax is used for grouping several expressions
|
||||||
they syntactically are treated as if they were one expression. This is
|
together so that they are treated as if they were one expression.
|
||||||
particularly important when syntactic expressions are used which only
|
This is particularly important when syntactic expressions are used
|
||||||
allow one expression, but the programmer wants to use more than one
|
which only allow one expression, but the programmer wants to use more
|
||||||
expression in that place. As an example, consider the conditional
|
than one expression in that place. As an example, consider the
|
||||||
expression below:
|
conditional expression below:
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(if (> x 0)
|
(if (> x 0)
|
||||||
|
@ -1103,10 +1167,10 @@ scm_foo (SCM s1, SCM s2)
|
||||||
scm_frame_begin (0);
|
scm_frame_begin (0);
|
||||||
|
|
||||||
c_s1 = scm_to_string (s1);
|
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);
|
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);
|
c_res = foo (c_s1, c_s2);
|
||||||
if (c_res == NULL)
|
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
|
reentered with a captured continuation. When you are prepared to handle
|
||||||
reentries, include @code{SCM_F_FRAME_REWINDABLE} in @var{flags}.
|
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
|
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
|
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
|
is indeed ended properly. If you fail to call @code{scm_end_frame}
|
||||||
@code{scm_begin_frame}, the behavior is undefined.
|
for each @code{scm_begin_frame}, the behavior is undefined.
|
||||||
@end deftypefn
|
@end deftypefn
|
||||||
|
|
||||||
@deftypefn {C Function} void scm_frame_end ()
|
@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}.
|
ends explicitly with @code{scm_frame_end}.
|
||||||
|
|
||||||
The function @code{scm_frame_unwind_with_scm} takes care that @var{data}
|
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
|
@end deftypefn
|
||||||
|
|
||||||
@deftypefn {C Function} void scm_frame_rewind (void (*func)(void *), void *data, scm_t_wind_flags flags)
|
@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.
|
as well.
|
||||||
|
|
||||||
The function @code{scm_frame_rewind_with_scm} takes care that @var{data}
|
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
|
@end deftypefn
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue