# 3.5.5功能程序的模块化和对象的模块化

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

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

正如我们在 [3.1.2](50)[ 3.1.2 ](50) 部分中进行了研究。

关键的模块化问题是我们希望对使用随机数的程序隐藏一个随机数生成器的内部状态。 我们从函数`rand_update`开始，其连续值提供了我们提供的随机数，并使用它来产生一个随机数生成器：

```js
function make_rand() {
    let x = random_init;
    function rand() {
        x = rand_update(x);
        return x;
    }
    return rand;
}
const rand = make_rand();
```

在流公式中，本身没有随机数生成器，只有连续调用`rand_update`产生的随机数流：

我们使用它来构造对`random_numbers`流中的连续对执行的Cesàro实验的结果流：

`cesaro_stream`现在被馈送到`monte_carlo`函数，该函数产生概率估计流。 然后将结果转换为![%5Cpi](img/868db68e71ec9cabbe333cb77dd676bc.jpg)的估算流。 该程序的该版本不需要一个参数即可告诉您要执行多少次试验。 通过更深入地查看`pi`流，可以更好地估算![%5Cpi](img/868db68e71ec9cabbe333cb77dd676bc.jpg)（通过执行更多的实验）：

这种方法具有相当大的模块化，因为我们仍然可以制定一个可以处理任意实验的通用`monte_carlo`函数。 但是，没有任务或本地状态。

<exercise>Exercise <ref name="ex:random-with-reset">[3.6](50#ex_3.6)</ref> discussed generalizing the random-number generator to allow one to reset the random-number sequence so as to produce repeatable sequences of <quote>random</quote> numbers. Produce a stream formulation of this same generator that operates on an input stream of requests to `generate` a new random number or to `reset` the sequence to a specified value and that produces the desired stream of random numbers. Don't use assignment in your solution.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_71_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>Redo exercise <ref name="ex:monte-carlo-integration">[3.5](50#ex_3.5)</ref> on Monte Carlo integration in terms of streams. The stream version of `estimate_integral` will not have an argument telling how many trials to perform. Instead, it will produce a stream of estimates based on successively more trials.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_71_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>

## 时间的功能编程视图

现在让我们回到本章开始时提出的对象和状态问题，并以新的视角对其进行研究。 我们引入了赋值和可变对象，以提供一种机制来模块化构建具有状态的系统的程序。 我们使用局部状态变量构造了计算对象，并使用赋值来修改这些变量。 我们通过相应的计算对象的时间行为来模拟世界上对象的时间行为。

现在我们已经看到，流提供了一种使用局部状态对对象进行建模的替代方法。 我们可以使用代表连续状态的时间历史的流来建模变化量，例如某个对象的局部状态。 本质上，我们使用流明确地表示时间，以便我们将模拟世界中的时间与评估期间发生的事件序列分离。 实际上，由于`delay`的存在，模型中的模拟时间与评估期间事件的顺序之间可能没有多少关系。

为了对比这两种建模方法，让我们重新考虑监视银行帐户余额的&lt;quote&gt;提款处理器&lt;/quote&gt;的实现。 在 [3.1.3](51) 部分中，我们实现了这种处理器的简化版本：

```js
function make_simplified_withdraw(balance) {
    function withdraw(amount) {
        balance = balance - amount;
        return balance;
    }
    return withdraw;
}
```

调用`make_simplified_withdraw`会生成计算对象，每个计算对象都有一个局部状态变量`balance`，该变量通过对对象的连续调用而递减。 该对象以`amount`作为参数并返回新的余额。 我们可以想象一个银行帐户的用户为这样的对象键入一系列输入，并观察显示屏上显示的返回值序列。

或者，我们可以将提款处理器建模为一个函数，该函数将余额和要提取的金额流作为输入，并在帐户中生成连续余额的流：

```js
function stream_withdraw(balance, amount_stream) {      
    return pair(balance,
                () => stream_withdraw(
                          balance - head(amount_stream),
                          stream_tail(amount_stream)));   
}
```

函数`stream_withdraw`实现定义明确的数学函数，其输出完全由其输入确定。 但是，假设输入`amount_stream`是用户键入的连续值流，并且显示结果余额流。 然后，从正在键入值和查看结果的用户的角度来看，流处理具有与`make_simplified_withdraw`创建的对象相同的行为。 但是，对于流版本，没有赋值，没有局部状态变量，因此，在 [3.1.3](51) 部分中，我们没有遇到任何理论上的困难。 但是系统有状态！

这真是了不起。 即使`stream_withdraw`实现了行为不发生变化的定义明确的数学函数，但用户在此处的感知仍是与状态变化的系统进行交互的一种。 解决此矛盾的一种方法是认识到，用户的时间存在将状态强加于系统上。 如果用户可以退出交互并从余额流而不是单个交易的角度进行思考，则系统将显示为无状态。 [[1]](71#footnote-1)

从复杂过程的一部分来看，其他部分似乎随时间而变化。 他们具有隐藏的时变本地状态。 如果我们希望使用计算机中的结构编写程序来模拟世界中这种自然分解的模型（从我们的观点来看，它是该世界的一部分），我们将使计算对象不起作用-它们必须随着 时间。 我们使用局部状态变量对状态进行建模，并通过分配这些变量来对状态变化进行建模。 通过这样做，我们将计算模型的执行时间定为我们所属的世界，因此我们在计算机中获得了&lt;quote&gt;对象&lt;/quote&gt;。

用对象建模是强大而直观的，主要是因为这与与我们所参与的世界进行交互的感知相匹配。 但是，正如我们在本章中反复看到的那样，这些模型提出了棘手的问题，即约束事件的顺序以及同步多个流程。 避免这些问题的可能性刺激了_功能编程语言_的发展，该语言不包含任何赋值或可变数据的规定。 在这种语言中，所有函数均实现其自变量的定义明确的数学函数，其行为不会改变。 功能方法对于处理并发系统非常有吸引力。 [[2]](71#footnote-2)

另一方面，如果我们仔细观察，也会发现与时间相关的问题也正在渗入功能模型。 当我们希望设计交互系统时，特别是在建模独立实体之间的交互的系统时，就会出现一个特别麻烦的领域。 例如，再次考虑实施允许联合银行帐户的银行系统。 在使用分配和对象的常规系统中，我们将通过建模使Peter和Paul共享一个帐户，方法是让Peter和Paul将他们的交易请求发送到同一银行帐户对象，如 [部分中所见 3.1.3](51)对象&lt;/quote&gt; 的情况下，我们已经表明，可以将银行帐户建模为对交易请求流进行操作的流程。 产生回应。 因此，我们可以通过合并Peter和Paul的交易请求流与Paul的请求流并将结果馈送到银行帐户流过程中来模拟Peter和Paul具有联合银行帐户的事实，如图 [所示 3.38](71#fig_3.38) 。

<figure>![](img/980218624aef66d9c577d93221afebf0.jpg)**[图3.38](71#fig_3.38)** 联合银行帐户，通过合并两个交易请求流来建模。</figure>

这种表述的问题在于_合并_的概念。 通过简单地交替接收来自Peter的一个请求和来自Paul的一个请求，合并两个流不会做。 假设Paul很少访问该帐户。 我们几乎不能强迫彼得等待保罗访问该帐户，然后他才能进行第二次交易。 但是，如果实现了这种合并，则它必须以某种方式交织两个事务流，这在Peter和Paul的感知下会受到&lt;quote&gt;实时&lt;/quote&gt;的限制，也就是说，如果Peter和Paul见面，他们可以达成共识 在会议之前处理了某些交易，而在会议之后处理了其他交易。 [[3]](71#footnote-3)

这与我们在 [3.4.1](64)正确的&lt;/quote&gt;顺序 具有状态的对象的并发处理中的事件数。 因此，为了支持功能样式，合并来自不同代理的输入的需求重新引入了功能样式本应消除的相同问题。

本章以构建计算模型为目标，该计算模型的结构与我们对要建模的真实世界的感知相匹配。 我们可以将世界建模为带有状态的独立的，有时限的，相互作用的对象的集合，也可以将世界建模为一个单一的，永恒的，无状态的统一体。 每种视图都有强大的优势，但是没有一种视图可以完全令人满意。 巨大的统一尚未出现。 [[4]](71#footnote-4)

* * *

[[1]](71#footnote-link-1) Similarly in physics, when we observe a moving particle, we say that the position (state) of the particle is changing. However, from the perspective of the particle's world line in space-time there is no change involved.

[[2]](71#footnote-link-2) John Backus, the inventor of Fortran, gave high visibility to functional programming when he was awarded the ACM Turing award in 1978\. His acceptance speech (<citation>Backus 1978</citation>) strongly advocated the functional approach. A good overview of functional programming is given in <citation>Henderson 1980</citation> and in <citation>Darlington, Henderson, and Turner 1982</citation>.

[[3]](71#footnote-link-3) Observe that, for any two streams, there is in general more than one acceptable order of interleaving. Thus, technically, <quote>merge</quote> is a relation rather than a function—the answer is not a deterministic function of the inputs. We already mentioned (footnote <ref name="foot:nondeterministic">**Cound not find label for foot:nondeterministic**</ref>) that nondeterminism is essential when dealing with concurrency. The merge relation illustrates the same essential nondeterminism, from the functional perspective.

[[4]](71#footnote-link-4) The object model approximates the world by dividing it into separate pieces. The functional model does not modularize along object boundaries. The object model is useful when the unshared state of the <quote>objects</quote> is much larger than the state that they share. An example of a place where the object viewpoint fails is quantum mechanics, where thinking of things as individual particles leads to paradoxes and confusions. Unifying the object view with the functional view may have little to do with programming, but rather with fundamental epistemological issues.

