dynamic-wind
The built-in dynamic-wind
procedure is Scheme’s equivalent to Common
Lisp’s unwind-protect
, the try ... finally ...
construct in
languages like C# and Java and so on.
The main entry point for this example is a provedure named
drill-hole
. It uses the simulated drill press described
earlier. The drill-hole
function defines several private functions
including start-drilling
, keep-drilling
and stop-drilling
that
are used in the arguments passed to dynamic-wind
in the body of
drill-hole
. As noted in the chapter on [lexical closures][] where
[make-drill][] was defined, the simulated drill press has a number of
run-time constraints such as that the drill motor can only be started
or stopped when the drill height is 0. The drill-hole
procedure is a
wrapper for an instance of such a simulated drill that uses
dynamic-wind
to protect the wrapped drill from violations of such
constraints even when its keep-drilling
loop is exited and
re-entered using a continuation.
The result of calling keep-drilling
is bound to the local variable
k
in the body of drill-hole
. That call to keep-drilling
is
protected during stack winding and unwinding by calls to
start-drilling
and stop-drilling
, respectively, by passing all
three functions in a call to dynamic-wind
.
Those familiar with languages with constructs like (unwind-protect ...)
or try ... finally ...
can understand the relationship between
keep-drilling
and stop-drilling
as being an enhanced version of
such constructs.
In other words, dynamic-wind
guarantees to call stop-drilling
after keep-drilling
exits, whether or not the latter exits normally
or through some non-sequential flow of control, as in the following
Common Lisp code:
(unwind-protect
(progn
(start-drilling)
(keep-drilling))
(stop-drilling))
Or in the case of languages like Java and C#, something like:
try {
startDrilling();
keepDrilling();
} finally {
stopDrilling();
}
In C++, similar stack-unwinding protections are achieved using “automatic” variables of types with destructors.
The additional functionality offered by dynamic-wind
over the
unwind-protect
and try ... finally ...
examples is that
dynamic-wind
also guarantees that start-drilling
will be called
before keep-drilling
starts execution the first time it is entered
and if it is re-entered by invoking a continuation after it has
previously exited.
This stack winding / unwinding / rewinding behavior is demonstrated in
the body of drill-hole
. In particular, keep-drilling
bores the
hole 50% through the full range of the possible bit positions and then
exits, returning a continuation that is later used to resume drilling
until the hole is 100% complete. Looking at the output,
one can see that start-drilling
and stop-drilling
are called the
appropriate number of times (twice each), in the appropriate places in
the overall flow of control (each time the body of keep-drilling
is
about to be entered and after each time keep-drilling
exits,
respectively).
Here is the Scheme source for drill-hole
:
;; -*- geiser-scheme-implementation: guile -*-
;; Copyright 2016 Kirk Rader
;;
;; Licensed under the Apache License, Version 2.0 (the "License"); you
;; may not use this file except in compliance with the License. You
;; may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
;; implied. See the License for the specific language governing
;; permissions and limitations under the License.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; A Scheme program that illustrates the use of dynamic-wind to
;; protect entry, exit and re-entry of an execution context using
;; continuations.
;;
;; This uses an object that simulates an automated drill press. The
;; simulated drill has various operational constraints, such as that
;; the main drilling motor can only be turned on or off when the bit
;; is in its highest position.
;;
;; The point of the example is to show how dynamic-wind can be used to
;; ensure that these constraints are taken into account by an ongoing
;; sequence of drilling commands even when that sequence might be
;; exited and then re-entered using continuations.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Function: (drill-hole)
;;
;; Usage: (drill-hole)
;;
;; Outputs a sequence of messages to the console indicating a drill's
;; state changes over the course of a program to bore a hole.
;;
;; This uses dynamic-wind to ensure that the drilling motor is safely
;; started and stopped each time the drilling program execution
;; context is entered, exited or re-entered using continuations.
;;
;; Returns a value that indicates the outcome of the drilling program.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (drill-hole)
(let ((drill (make-drill)))
(letrec ((start-drilling
;; turn on the motor
;;
;; assumes that motor is currently stopped and that bit
;; position is 0
;;
;; this will be used as the "before thunk" in a call to
;; dynamic-wind
(lambda ()
(display "start-drilling called")
(newline)
(drill 'start-motor!)))
(stop-drilling
;; raise the bit and stop the motor
;;
;; assumes that motor is currently running
;;
;; this will be used as the "after thunk" in a call to
;; dynamic-wind
(lambda ()
(display "stop-drilling called")
(newline)
(drill 'move-bit! 0)
(drill 'stop-motor!)))
(keep-drilling
;; the drilling program
;;
;; assumes the motor is currently running
;;
;; stops half-way through to let the bit cool, returning a
;; continuation by which drilling can be resumed and
;; continue to completion
;;
;; returns 'done when the hole is completely drilled
(lambda ()
(call/cc
(lambda (return)
(display "keep-drilling entered")
(newline)
(drill 'move-bit! 50)
(display "hole 50% drilled, pausing to let bit cool")
(newline)
(let ((command (call/cc (lambda (k) (return k)))))
(display "keep-drilling re-entered")
(newline)
(if (eq? command 'continue)
(begin
(display "continuing to drill")
(newline)
(drill 'move-bit! 100)
(display "hole completely drilled")
(newline)
'done)
(begin
(display "drilling program canceled")
(newline))))))))
(k
;; bind k to the result of a call to keep-drilling,
;; protected during stack winding and unwinding by calls to
;; start-drilling and stop-drilling, respectively
(dynamic-wind
;; ensure drill motor is running every time keep-drilling
;; is entered or re-entered
start-drilling
;; drill the hole
keep-drilling
;; ensure drill motor is stopped every time keep-drilling
;; exits
stop-drilling)))
;; we get here twice:
;;
;; 1. when keep-drilling returns a continuation half-way through
;; the drilling process
;;
;; 2. when keep-drilling ultimately completes as a result of the
;; invocation of k, below
;;
;; in *both* cases, the preceding call to dynamic-wind ensures
;; that start-drilling is called before the body of
;; keep-drilling is entered and stop-drilling is called after
;; the body of keep-drilling exits, even though those entries
;; and exits happen at different times and at different points
;; in the flow of control
(if (procedure? k)
;; k is a continuation procedure when keep-drilling exits
;; half-way throuh the drilling process
(begin
(display "waiting a bit (pun intended)")
(newline)
;; use k to resume drilling
;;
;; keep-drilling will not exit again until the hole is
;; completely bored unless an exception is thrown
(k 'continue))
;; when keep-drilling finishes boring the hole completely, it
;; returns a value that is not a procedure
;;
;; return that final value as the result of drill-hole
k))))
(download drill-hole.scm)
Here is the output of (drill-hole):
start-drilling called
started motor
keep-drilling entered
stepping bit position from 0 to 50 by 1
hole 50% drilled, pausing to let bit cool
stop-drilling called
stepping bit position from 50 to 0 by -1
motor stopped
waiting a bit (pun intended)
start-drilling called
started motor
keep-drilling re-entered
continuing to drill
stepping bit position from 0 to 100 by 1
hole completely drilled
stop-drilling called
stepping bit position from 100 to 0 by -1
motor stopped