Kirk Rader  1.0-SNAPSHOT
dynamic-wind

Protected logic during stack unwinding and rewinding.

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 to demonstrate the use of dynamic-wind. 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 call/cc.

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).

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))))

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