Streams and Delayed Evaluation

The `integral` procedure at the end of the preceding section shows
how we can use streams to model signal-processing systems that contain
feedback loops. The feedback loop for the adder shown in
figure is modeled by the fact that `integral`'s
internal stream `int` is defined in terms of itself:

(define int (cons-stream initial-value (add-streams (scale-stream integrand dt) int)))The interpreter's ability to deal with such an implicit definition depends on the

Unfortunately, stream models of systems with loops
may require uses of `delay` beyond the ``hidden'' `delay`
supplied by `cons-stream`. For instance,
figure shows a signal-processing system for
solving the
differential equation
*dy*/*dt*=*f*(*y*) where *f* is a given
function. The figure shows a mapping component, which
applies *f* to its input signal, linked in a feedback loop to an
integrator in a manner very similar to that of the analog computer
circuits that are actually used to solve such equations.

Assuming we are given an initial value *y*_{0} for *y*, we
could try to model this system using the procedure

(define (solve f y0 dt) (define y (integral dy y0 dt)) (define dy (stream-map f y)) y)This procedure does not work, because in the first line of

On the other hand, the intent of our definition does make sense,
because we can, in principle, begin to generate the `y` stream
without knowing `dy`. Indeed, `integral` and many other
stream operations have properties similar to those of `
cons-stream`, in that we can generate part of the answer given only
partial information about the arguments. For `integral`, the
first element of the output stream is the specified `
initialvalue`. Thus, we can generate the first element of the output
stream without evaluating the integrand `dy`. Once we know the
first element of `y`, the `stream-map` in the second line of
`solve` can begin working to generate the first element of `
dy`, which will produce the next element of `y`, and so on.

To take advantage of this idea, we will redefine `integral` to
expect the integrand stream to be a
*delayed argument*. `
Integral` will `force` the integrand to be evaluated only when it
is required to generate more than the first element of the output stream:

(define (integral delayed-integrand initial-value dt) (define int (cons-stream initial-value (let ((integrand (force delayed-integrand))) (add-streams (scale-stream integrand dt) int)))) int)Now we can implement our

(define (solve f y0 dt) (define y (integral (delay dy) y0 dt)) (define dy (stream-map f y)) y)In general, every caller of

(stream-ref (solve (lambda (y) y) 1 0.001) 1000) 2.716924

**Exercise.**
The `integral` procedure used above was analogous to the
``implicit'' definition of the infinite stream of integers in
section . Alternatively, we can give a
definition of `integral` that is more like `
integers-starting-from` (also in section ):

(define (integral integrand initial-value dt) (cons-stream initial-value (if (stream-null? integrand) the-empty-stream (integral (stream-cdr integrand) (+ (* dt (stream-car integrand)) initial-value) dt))))When used in systems with loops, this procedure has the same problem as does our original version of

**Exercise.**

Consider the problem of designing a signal-processing system to study the homogeneous second-order linear differential equation

The output stream, modeling

**Exercise.**
Generalize the `solve-2nd` procedure of exercise so
that it can be used to solve general second-order differential
equations
.

**Exercise.**
A *series RLC circuit* consists of a resistor, a capacitor, and an
inductor connected in series, as shown in figure .
If *R*, *L*, and *C* are the resistance, inductance, and capacitance,
then the relations between voltage (*v*) and current (*i*)
for the three components are described by the equations

and the circuit connections dictate the relations

Combining these equations shows that the state of the circuit (summarized by

The signal-flow diagram representing this system of differential equations is shown in figure .

Write a procedure `RLC` that takes as arguments the parameters
*R*, *L*, and *C* of the circuit and the time increment *dt*. In a
manner similar to that of the `RC` procedure of
exercise , `RLC` should produce a procedure
that takes the initial values of the state variables, *v*_{C<<14060>>0} and
*i*_{L<<14061>>0}, and produces a pair (using `cons`) of the streams of
states *v*_{C} and *i*_{L}. Using `RLC`, generate the pair of
streams that models the behavior of a series RLC circuit with *R* = 1
ohm, *C*= 0.2 farad, *L* = 1 henry, *dt* = 0.1 second, and initial
values
*i*_{L<<14066>>0} = 0 amps and
*v*_{C<<14067>>0} = 10 volts.

Normal-order evaluation

The examples in this section illustrate how the explicit use of `
delay` and `force` provides great programming flexibility, but the
same examples also show how this can make our programs more complex.
Our new `integral` procedure, for instance, gives us the power to
model systems with loops, but we must now remember that `integral`
should be called with a delayed integrand, and every procedure that
uses `integral` must be aware of this. In effect, we have created
two classes of procedures: ordinary procedures and procedures that
take delayed arguments. In general, creating separate classes of
procedures forces us to create separate classes of higher-order
procedures as well.
^{}

One way to avoid the need for two different classes of procedures is
to make all procedures take delayed arguments. We could adopt a model
of evaluation in which all arguments to procedures are automatically
delayed and arguments are forced only when they are actually needed
(for example, when they are required by a primitive operation). This
would transform our language to use normal-order evaluation, which we
first described when we introduced the substitution model for
evaluation in section . Converting to
normal-order evaluation provides a uniform and elegant way to simplify
the use of delayed evaluation, and this would be a natural strategy to
adopt if we were concerned only with stream processing. In
section , after we have studied the evaluator, we
will see how to transform our language in just this way.
Unfortunately, including delays in procedure calls wreaks havoc with
our ability to design programs that depend on the order of events,
such as programs that use assignment, mutate data, or perform input or
output. Even the single `delay` in `cons-stream` can cause
great confusion, as illustrated by exercises
and . As far as anyone knows, mutability and delayed
evaluation do not mix well in programming languages, and devising ways
to deal with both of these at once is an active area of research.