# 3.5.4流和延迟评估

> 译者： [https://sicp.comp.nus.edu.sg/chapters/70](https://sicp.comp.nus.edu.sg/chapters/70)

<subsection wip="yes">Note: this section is a work in progress!

上一节末尾的`integral`函数说明了如何使用流对包含反馈回路的信号处理系统进行建模。 如图 [3.32](69#fig_3.32) 所示，用于加法器的反馈环路是根据以下事实来建模的：`integral`的内部流`int`是根据其自身定义的：

```js
const integ = pair(initial_value, 
    () => add_streams(scale_stream(integrand, dt),
                      integ);
        );
```

<split>The interpreter's ability to deal with such an implicit definition depends on the delay resulting from wrapping the call of `add_streams` into a function definition. Without this delay, the interpreter could not construct `integ` before evaluating both arguments to `pair`, which would require that `integ` already be defined. In general, such a delay is crucial for using streams to model signal-processing systems that contain loops. Without a delay, our models would have to be formulated so that the inputs to any signal-processing component would be fully evaluated before the output could be produced. This would outlaw loops.</split> <split>不幸的是，具有循环的系统的流模型可能需要使用迄今为止到目前为止所看到的流编程模式之外的延迟。 例如，图 [3.34](70#fig_3.34) 显示了用于求解微分方程![dy%2Fdt%3Df%28y%29](img/f31af9bb1202b642394fbd62188e25a7.jpg)的信号处理系统，其中![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)是给定函数。 该图显示了一个映射组件，该组件将![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)应用于其输入信号，并以与实际用于求解此类方程式的模拟计算机电路非常相似的方式在反馈环路中链接至积分器。

<figure>![](img/54a1b3b77d4df64d89a603ff509f034b.jpg)**[图3.34](70#fig_3.34)** 一种求解方程</figure>

的<quote>模拟计算机电路</quote>。</split>

假设给定![y](img/c592009395c2de830215c39f7bb6f97b.jpg)的初始值![y_0](img/54676c9601d846908e9d5fffa5766413.jpg)，我们可以尝试使用函数对系统进行建模

```js
function solve(f, y0, dt) {
    const y = integral(dy, y0, dt);
    const dy = stream_map(f, y);
    return y;
}
```

This function does not work, because in the first line of `solve` the call to `integral` requires that the input `dy` be defined, which does not happen until the second line of `solve`.

另一方面，我们定义的意图确实是有道理的，因为原则上我们可以在不知道`dy`的情况下开始生成`y`流。 实际上，`integral`和许多其他流操作仅在提供有关自变量的部分信息的情况下才能生成部分答案。 对于`integral`，输出流的第一个元素是指定的`initial_value`。 因此，我们可以生成输出流的第一个元素，而无需评估被积数`dy`。 一旦我们知道`y`的第一个元素，`solve`第二行中的`stream_map`就可以开始生成`dy`的第一个元素，这将产生`y`的下一个元素，依此类推 。

为了利用这个想法，我们将重新定义`integral`以期望被积流是_延迟参数_。 仅当需要生成比输出流的第一个元素更多的函数时，函数`integral`才会强制对积分对象求值：

```js
function integral(delayed_integrand, intial_value, dt) {
    const integrand = delayed_integrand();
    const integ =      
        pair(intial_value,
            add_streams(scale_stream(integrand, dt), int));
}
```

现在，我们可以通过延迟`y`定义中对`dy`的求值来实现`solve`功能：

```js
function solve(f, y0, dt) {
    const y = integral( () => dy, y0, dt);
    const dy = stream_map(f, y);
    return y;
}
```

通常，每个`integral`的调用者现在都必须延迟被积分参数。 我们可以通过计算初始条件为![y%280%29%3D1](img/78bc8745dcf0c7b9c0a4633a47c29e22.jpg)的微分方程![dy%2Fdt%3Dy](img/6ea1fc36924c28e2609283cb855f94f7.jpg)的解的![y%3D1](img/61d5c3d6a0f74cb4f7f6db824ecfbd53.jpg)处的值来近似![e%5Capprox%202.718](img/a202b19e79f51690c4c16054f3d04844.jpg)来证明`solve`函数的工作：

```js
stream_ref(solve(y => y, 1, 0.001), 1000);
```

<exercise>The `integral` function used above was analogous to the <quote>implicit</quote> definition of the infinite stream of integers in section <ref name="sec:infinite-streams">[3.5.2](68)</ref>. Alternatively, we can give a definition of `integral` that is more like `integers-starting-from` (also in section <ref name="sec:infinite-streams">[3.5.2](68)</ref>):

```js
function integral(integrand, intial_value, dt) {
    return pair(intial_value,
        is_null(integrand) ? null
            : integral(stream_tail(integrand),
                dt * head(integrand) + initial_value,
	        dt));
}
```

When used in systems with loops, this function has the same problem as does our original version of `integral`. Modify the function so that it expects the `integrand` as a delayed argument and hence can be used in the `solve` function shown above.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_70_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise> <exercise><figure>![](img/27a1f9e4ccebf1e4513ec43e0ea77fc9.jpg)**[Figure 3.35](70#fig_3.35)** Signal-flow diagram for the solution to a second-order linear differential equation.</figure>

Consider the problem of designing a signal-processing system to study the homogeneous second-order linear differential equation ![%20%5Cfrac%20%7Bd%5E%7B2%7D%20y%7D%7Bdt%5E%7B2%7D%7D-a%5Cfrac%7Bdy%7D%7Bdt%7D-by%3D0%20](img/c01e1557d477b60d3cc24aaa6c2540c0.jpg) The output stream, modeling ![y](img/c592009395c2de830215c39f7bb6f97b.jpg), is generated by a network that contains a loop. This is because the value of ![d%5E%7B2%7Dy%2Fdt%5E%7B2%7D](img/378acaf4f1735ba70824f04371b9805e.jpg) depends upon the values of ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) and ![dy%2Fdt](img/f02ae66a98f2a2cd839b449380434dfb.jpg) and both of these are determined by integrating ![d%5E%7B2%7Dy%2Fdt%5E%7B2%7D](img/378acaf4f1735ba70824f04371b9805e.jpg). The diagram we would like to encode is shown in Figure <ref name="fig:2nd-order">[3.35](70#fig_3.35)</ref>. Write a function `solve_2nd` that takes as arguments the constants ![a](img/070b1af5eca3a5c5d72884b536090f17.jpg), ![b](img/6872867a863714d15d9a0d64c20734ce.jpg), and ![dt](img/a2365da0b6a89ff65d272723bd424d5a.jpg) and the initial values ![y_%7B0%7D](img/85a8bef65012a8167faa1268545d5b5f.jpg) and ![dy_%7B0%7D](img/8bd366ccbdfab02b83918790effd99e4.jpg) for ![y](img/c592009395c2de830215c39f7bb6f97b.jpg) and ![dy%2Fdt](img/f02ae66a98f2a2cd839b449380434dfb.jpg) and generates the stream of successive values of ![y](img/c592009395c2de830215c39f7bb6f97b.jpg).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_70_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Generalize the `solve-2nd` function of exercise <ref name="ex:2nd-order">[3.78](70#ex_3.78)</ref> so that it can be used to solve general second-order differential equations ![d%5E%7B2%7D%20y%2Fdt%5E%7B2%7D%3Df%28dy%2Fdt%2C%5C%2C%20y%29](img/1be3cbd7aaf7e80c72cad2d0cb88281e.jpg).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_70_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>A _series RLC circuit_ consists of a resistor, a capacitor, and an inductor connected in series, as shown in Figure <ref name="fig:series-rlc">[3.36](70#fig_3.36)</ref>. If ![R](img/502cdd9c79852b33d2a6d18ba5ec3102.jpg), ![L](img/db4a9fef02111450bf98261889de550c.jpg), and ![C](img/6c8feca3b2da3d6cf371417edff4be4f.jpg) are the resistance, inductance, and capacitance, then the relations between voltage (![v](img/a7b0439b75ff3fa542a86700c6afc69a.jpg)) and current (![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)) for the three components are described by the equations \begin{eqnarray*} v_{R} &=& i_{R} R\\ v_{L} &=& L\frac{di_{L}}{dt}\\ i_{C} &=& C\frac{dv_{C}}{dt} \end{eqnarray*} and the circuit connections dictate the relations \begin{eqnarray*} i_{R} &=& i_{L}=-i_{C}\\ v_{C} &=& v_{L}+v_{R} \end{eqnarray*} Combining these equations shows that the state of the circuit (summarized by ![v_%7BC%7D](img/9e804d3d55d43ab4fbd709c6f6318012.jpg), the voltage across the capacitor, and ![i_%7BL%7D](img/70d5bfb5cf97212dafd19d528a05d48b.jpg), the current in the inductor) is described by the pair of differential equations \begin{eqnarray*} \frac{dv_{C}}{dt} &=& -\frac{i_{L}}{C}\\ \frac {di_{L}}{dt} &=& \frac{1}{L}v_{C}-\frac{R}{L}i_{L} \end{eqnarray*} The signal-flow diagram representing this system of differential equations is shown in Figure <ref name="fig:rlc-signal-flow">[3.37](70#fig_3.37)</ref>.

<figure>![](img/6beaa071d44086a762df5ee5f4bd2659.jpg)**[Figure 3.36](70#fig_3.36)** A series RLC circuit.</figure>

<figure>![](img/d903a95eb0d9ea09b6453e8c09edcffc.jpg)**[Figure 3.37](70#fig_3.37)** A signal-flow diagram for the solution to a series RLC circuit.</figure>

Write a function `RLC` that takes as arguments the parameters ![R](img/502cdd9c79852b33d2a6d18ba5ec3102.jpg), ![L](img/db4a9fef02111450bf98261889de550c.jpg), and ![C](img/6c8feca3b2da3d6cf371417edff4be4f.jpg) of the circuit and the time increment ![dt](img/a2365da0b6a89ff65d272723bd424d5a.jpg). In a manner similar to that of the `RC` function of exercise <ref name="ex:rc-circuit">[3.73](69#ex_3.73)</ref>, `RLC` should produce a function that takes the initial values of the state variables, ![v_%7BC_%7B0%7D%7D](img/ba5710ff8f078429ca051677e5d39faf.jpg) and ![i_%7BL_%7B0%7D%7D](img/af7d59ce9dd9959068d784fe4d93e1e2.jpg), and produces a pair (using `pair`) of the streams of states ![v_%7BC%7D](img/9e804d3d55d43ab4fbd709c6f6318012.jpg) and ![i_%7BL%7D](img/70d5bfb5cf97212dafd19d528a05d48b.jpg). Using `RLC`, generate the pair of streams that models the behavior of a series RLC circuit with ![R%20%3D%201](img/6ee24986e6ee24f1b2bc394523a4b2f3.jpg) ohm, ![C%3D%200.2](img/61e7542b0d97e5120a2a280e524a4d8c.jpg) farad, ![L%20%3D%201](img/7a3a560d32e1d1a4f5566c549f7a8050.jpg) henry, ![dt%20%3D%200.1](img/55f99d1b7a83ff729ff9894f918472b1.jpg) second, and initial values ![i_%7BL_%7B0%7D%7D%20%3D%200](img/7c010f5085352b2de8a67bbef1871015.jpg) amps and ![v_%7BC_%7B0%7D%7D%20%3D%2010](img/45749939c413d47dddb407c01392fc4e.jpg) volts.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_70_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

## 正常顺序评估

本节中的示例说明了延迟求值如何提供出色的编程灵活性，但是相同的示例也说明了这种方法如何使我们的程序更复杂。 例如，我们新的`integral`函数使我们能够使用循环对系统进行建模，但是我们现在必须记住，`integral`应该使用延迟的被积数进行调用，并且使用`integral`的每个函数都必须意识到这一点 。 实际上，我们创建了两类函数：普通函数和带有延迟参数的函数。 通常，创建单独的函数类会迫使我们也创建单独的高阶函数类。 [[1]](70#footnote-1)

避免需要两个不同类的函数的一种方法是使所有函数都采用延迟参数。 我们可以采用一种评估模型，在该模型中，函数的所有参数都将自动延迟，并且仅在实际需要时才强制使用参数（例如，当原始操作需要它们时）。 这将使我们的语言转换为使用正常顺序评估，这是在 [1.1.5](7)[4.2](81)[3.51](67#ex_3.51)[3.52](67#ex_3.52) 所示。 据任何人所知，可变性和延迟评估在编程语言中并不能很好地融合在一起，因此想出一种同时处理这两者的方法是一个活跃的研究领域。

* * *

[[1]](70#footnote-link-1) This is a small reflection, in JavaScript, of the difficulties that conventional strongly typed languages such as Pascal have in coping with higher-order functions. In such languages, the programmer must specify the data types of the arguments and the result of each function: number, logical value, sequence, and so on. Consequently, we could not express an abstraction such as <quote>map a given function `fun` over all the elements in a sequence</quote> by a single higher-order function such as `stream_map`. Rather, we would need a different mapping function for each different combination of argument and result data types that might be specified for a `fun`. Maintaining a practical notion of <quote>data type</quote> in the presence of higher-order functions raises many difficult issues. One way of dealing with this problem is illustrated by the language ML (<citation>Gordon, Milner, and Wadsworth 1979</citation>), whose <quote>polymorphic data types</quote> include templates for higher-order transformations between data types. Moreover, data types for most functions in ML are never explicitly declared by the programmer. Instead, ML includes a _type-inferencing_ mechanism that uses information in the environment to deduce the data types for newly defined functions.

