# 1.3.4作为返回值的功能

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

以上示例说明了将函数作为参数传递的能力如何显着增强了我们编程语言的表达能力。 通过创建返回值本身就是函数的函数，我们甚至可以发挥更大的表达能力。

我们可以通过再次查看在 [1.3.3](21) 小节末尾描述的定点示例来说明这种想法。 我们从观察到![%5Csqrt%7Bx%7D](img/79b99d999f2750154be2c1edaace3bd7.jpg)是函数![y%5Cmapsto%20x%2Fy](img/0c8a358f0ea7528545e64afdb5977df8.jpg)的定点开始，制定了新版本的平方根函数作为定点搜索。 然后，我们使用平均阻尼来使近似收敛。 平均阻尼本身就是一种有用的通用技术。 即，给定一个函数![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)，我们考虑其![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)处的值等于![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)和![f%28x%29](img/1dd5fe70f6ae332b21a84a290e35e5ed.jpg)的平均值的函数。

我们可以通过以下函数表达平均阻尼的思想：

```js
function average_damp(f) {
    return x => average(x, f(x));
}
```

函数`average_damp`是将函数`f`作为其参数并返回一个函数（由函数定义表达式生成）作为其值的函数，该函数在应用于数字`x`时会产生`x`的平均值 ]和`f(x)`。 例如，将`average_damp`应用于`square`函数会生成一个函数，该函数的某个值![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)是![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)和![x%5E2](img/0bde6312d100e6dafaaf0861ae5b29fb.jpg)的平均值。 将此结果函数应用于10将返回平均值10和100，或55： [[1]](22#footnote-1)

```js
average_damp(square)(10);
```

使用`average_damp`，我们可以重新构造平方根函数，如下所示：

```js
function sqrt(x) {
    return fixed_point(average_damp(y => x / y),
                       1.0);
}
```

请注意，此公式是如何使方法中的三个思想明确的：定点搜索，平均阻尼和函数![y%5Cmapsto%20x%2Fy](img/0c8a358f0ea7528545e64afdb5977df8.jpg)。 将平方根方法的这种表示形式与 [ 1.1.7 ](9) 部分中给出的原始版本进行比较是有益的。 请记住，这些函数表示相同的过程，并且请注意，当我们根据这些抽象表示过程时，这种想法变得更加清晰。 通常，有很多方法可以将过程表达为函数。 经验丰富的程序员知道如何选择特别明显的流程公式，以及流程的有用元素在哪里显示为可在其他应用程序中重用的单独实体。 作为简单的重用示例，请注意![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)的立方根是函数![y%5Cmapsto%20x%2Fy%5E2](img/c758099a62e3fe937262edf777df4e1e.jpg)的固定点，因此我们可以立即将平方根函数推广为提取立方根的函数： [[2]](22#footnote-2)

```js
function cube_root(x) {
    return fixed_point(average_damp(y => x / square(y)),
                       1.0);
}
```

## 牛顿法

当我们在 [1.1.7](9) 部分中首次介绍平方根函数时，我们提到这是_牛顿法_的特例。 如果![x%5Cmapsto%20g%28x%29](img/f84f2866a24fd7c3d278190c81dce068.jpg)是可微函数，则方程![g%28x%29%3D0](img/d1a54dabdac8aad9137ed88c6e5b1f55.jpg)的解是函数![x%5Cmapsto%20f%28x%29](img/63958141c6aa3b08939d8cfc2224f7ce.jpg)的不动点，其中![%0A%20%20%20%20%20%20f%28x%29%20%3D%20x%20-%20%5Cfrac%7Bg%28x%29%7D%7BDg%28x%29%7D%0A%20%20%20%20%20%20](img/eed08fc487987b7bbfadd77f67c4bb38.jpg)和![Dg%28x%29](img/fe21e2061f8d5a38cc8faacd01840e16.jpg)是在![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)求值的![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg)的导数。 牛顿法是使用上面看到的定点方法，通过找到函数![f](img/dad139946fdd1363ecd86d20700a53a6.jpg)的不动点来近似方程的解。 [[3]](22#footnote-3) 对于许多功能![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg)以及对于![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)足够好的初始猜测，牛顿方法非常迅速地收敛到![g%28x%29%3D0](img/d1a54dabdac8aad9137ed88c6e5b1f55.jpg)的解。 [ [4]](22#footnote-4)

为了将牛顿方法实现为函数，我们必须首先表达导数的思想。 请注意，&lt;quote&gt;导数&lt;/quote&gt;类似于平均阻尼，是将一个函数转换为另一个函数的东西。 例如，函数![x%5Cmapsto%0A%20%20%20%20x%5E3%3Cimg%20alt%3D%22%2520is%2520the%2520function%2520%22%20src%3D%22https%3A%2F%2Fwww.zhihu.com%2Fequation%3Ftex%3D%2520is%2520the%2520function%2520%22%20%2F%3Ex%20%5Cmapsto%203x%5E2](img/5951a1f0bc12a24ec6fa44023147536d.jpg)的导数。 通常，如果![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg)是一个函数而![dx](img/595cae073ed203ff7de6cc8107331c70.jpg)是一个小数，则![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg)的导数![Dg](img/342f0307e4bdc54e765d45b9d0e909ed.jpg)是一个函数，其值在任何数字![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)处都被给出（在小[HTG8的范围内 ]）![Dg%28x%29%20%3D%20%5Cfrac%20%7Bg%28x%2Bdx%29%20-%20g%28x%29%7D%7Bdx%7D%20](img/99952706c9355ca31c53f5a19c6d5819.jpg)的形式，因此，我们可以将导数的思想（以![dx](img/595cae073ed203ff7de6cc8107331c70.jpg)为0.00001）作为函数来表达

```js
function deriv(g) {
    return x => (g(x + dx) - g(x)) / dx;
}
```

along with the declaration

```js
const dx = 0.00001;
```

像`average_damp`一样，`deriv`是一个以函数作为参数并返回函数作为值的函数。 例如，将![x%20%5Cmapsto%20x%5E3](img/4607ceead2499224f0d3718c3eee86e1.jpg)的导数近似为5（精确值为75），我们可以估算

```js
function cube(x) { return x * x * x; }

deriv(cube)(5);
```

借助于`deriv`，我们可以将牛顿法表示为定点过程：

```js
function newton_transform(g) {
   return x => x - g(x) / deriv(g)(x);
}
function newtons_method(g, guess) {
   return fixed_point(newton_transform(g), guess);
}
```

`newton_transform`函数在本节的开头表示公式，因此很容易定义`newtons_method`。 它以一个函数作为参数作为参数，该函数计算我们要为其找到零的函数以及一个初始猜测。 例如，要查找![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)的平方根，我们可以使用牛顿方法从初始猜测1开始查找函数![y%5Cmapsto%20y%5E2-x](img/b71def3ad4f962c7089fbff3a39a3a4b.jpg)的零。 [[5]](22#footnote-5) 这提供了另一种形式 的平方根函数：

```js
function sqrt(x) {
    return newtons_method(y => square(y) - x,
                          1.0);
}
```

## 抽象和一流的功能

我们已经看到了两种将平方根计算表示为更通用方法的实例的方法，一种是定点搜索，另一种是使用牛顿方法。 由于牛顿方法本身被表示为不动点过程，因此我们实际上看到了两种将平方根计算为不动点的方法。 每种方法都以一个函数开始，并找到该函数进行某种转换的一个固定点。 我们可以将此一般概念本身表达为一个函数：

```js
function fixed_point_of_transform(g, transform, guess) {
    return fixed_point(transform(g), guess);
}
```

这个非常通用的函数将计算某些函数的函数`g`，转换`g`的函数以及初始猜测作为参数。 返回的结果是转换函数的固定点。

使用这种抽象，我们可以重现此部分中的第一个平方根计算（在其中寻找![y%20%5Cmapsto%20x%2Fy](img/d11aa49c178cf017ca78223de49e426c.jpg)的平均阻尼版本的固定点），作为该常规方法的实例：

```js
function sqrt(x) {
    return fixed_point_of_transform(
               y => x / y,
               average_damp,
               1.0);
}
```

同样，我们可以将本节中的第二个平方根计算表示为（找到![y%5Cmapsto%20y%5E2-x](img/b71def3ad4f962c7089fbff3a39a3a4b.jpg)的牛顿变换的固定点的牛顿方法的实例）。

```js
function sqrt(x) {
    return fixed_point_of_transform(
               y => square(y) - x,
               newton_transform,
               1.0);
}
```

我们从 [1.3](18) 开始，观察到复合函数是至关重要的抽象机制，因为复合函数允许我们将通用计算方法表达为编程语言中的显式元素。 现在，我们已经看到了高阶函数如何允许我们操纵这些通用方法来创建进一步的抽象。

作为程序员，我们应该警惕有机会识别程序中的基础抽象，并在它们的基础上进行泛化，以创建更强大的抽象。 这并不是说总是应该以最抽象的方式编写程序； 专业的程序员知道如何选择适合其任务的抽象级别。 但是重要的是要能够根据这些抽象思想进行思考，以便我们可以随时将它们应用到新的上下文中。 高阶函数的重要性在于，它们使我们能够将这些抽象形式明确表示为编程语言中的元素，以便可以像处理其他计算元素一样处理它们。

通常，编程语言对可操纵计算元素的方式施加了限制。 限制最少的元素据说具有_一流的_状态。 一等元素的某些&lt;quote&gt;权利和特权&lt;/quote&gt;为： [[6]](22#footnote-6)

*   可以使用名称来引用它们。
*   它们可以作为参数传递给函数。
*   它们可能作为函数的结果返回。
*   它们可能包含在数据结构中。 [[7]](22#footnote-7)

与其他常见的编程语言不同，JavaScript授予功能完全一流的地位。 这对有效实施提出了挑战，但由此产生的表达能力却是巨大的。 [[8]](22#footnote-8)

<exercise>Declare a function `cubic` that can be used together with the `newtons_method` function in expressions of the form

```js
newtons_method(cubic(a, b, c), 1);
```

to approximate zeros of the cubic ![x%5E3%20%2Bax%5E2%20%2Bbx%20%2Bc](img/016cffa75511865c5b94d6550a089181.jpg).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_1_div">Solution</button> <solution>```js
function cubic(a, b, c) {
    return x => cube(x) + a * square(x) + b * x + c;
}
```</solution></exercise> <exercise>Declare a function `double` that takes a function of one argument as argument and returns a function that applies the original function twice. For example, if `inc` is a function that adds 1 to its argument, then `double(inc)` should be a function that adds 2\. What value is returned by

```js
double(double(double))(inc)(5); // ?
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_2_div">Solution</button> <solution>```js
function double(f) {
    return x => f(f(x));
}
```</solution></exercise> <exercise>Let ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) and ![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg) be two one-argument functions. The _composition_ ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) after ![g](img/3ffa1e2936f7bf2ddb00e4aed40dd008.jpg) is defined to be the function ![x%5Cmapsto%20f%28g%28x%29%29](img/c7a00a6cd76af006829a4d5f17ca5a7e.jpg). Declare a function `compose` that implements composition. For example, if `inc` is a function that adds 1 to its argument,

```js
compose(square, inc)(6);
```

returns 49.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_3_div">Solution</button> <solution>```js
function compose(f, g) {
    return x => f(g(x));
}
```</solution></exercise> <exercise>If ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is a numerical function and ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) is a positive integer, then we can form the ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th repeated application of ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg), which is defined to be the function whose value at ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) is ![f%28f%28%5Cldots%28f%28x%29%29%5Cldots%29%29](img/aedc6388ccb95d9de6e59dcea4fc21d1.jpg). For example, if ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is the function ![x%20%5Cmapsto%20x%2B1](img/6363378634855ae9e1918487e2ab2a5e.jpg), then the ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th repeated application of ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is the function ![x%20%5Cmapsto%20x%2Bn](img/2b372296e9e31bba9ae8cbe364d41cfa.jpg). If ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is the operation of squaring a number, then the ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th repeated application of ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is the function that raises its argument to the ![2%5En](img/ad8f11eb09ffc143b0bcfd9e65d69be4.jpg)th power. Write a function that takes as inputs a function that computes ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) and a positive integer ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) and returns the function that computes the ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th repeated application of ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg). Your function should be able to be used as follows:

```js
repeated(square, 2)(5);
```

Hint: You may find it convenient to use `compose` from exercise <ref name="ex:compose">[1.42](22#ex_1.42)</ref>.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_4_div">Solution</button> <solution>```js
function repeated(f, n) {
    return n === 0
           ? x => x
           : compose(f, repeated(f, n - 1));
}
```</solution></exercise> <exercise>The idea of _smoothing_ a function is an important concept in signal processing. If ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is a function and ![dx](img/595cae073ed203ff7de6cc8107331c70.jpg) is some small number, then the smoothed version of ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) is the function whose value at a point ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) is the average of ![f%28x-dx%29](img/003f58b89bb541aaca7cd706da785cd7.jpg), ![f%28x%29](img/1dd5fe70f6ae332b21a84a290e35e5ed.jpg), and ![f%28x%2Bdx%29](img/276850c24a6b91c2c452001b176f876c.jpg). Write a function `smooth` that takes as input a function that computes ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg) and returns a function that computes the smoothed ![f](img/dad139946fdd1363ecd86d20700a53a6.jpg). It is sometimes valuable to repeatedly smooth a function (that is, smooth the smoothed function, and so on) to obtained the _![n](img/493731e423d5db62086d0b8705dda0c8.jpg)-fold smoothed function_. Show how to generate the ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)-fold smoothed function of any given function using `smooth` and `repeated` from exercise <ref name="ex:repeated">[1.43](22#ex_1.43)</ref>.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_5_div">Solution</button> <solution>```js
const dx = 0.00001;
function smooth(f) {
    return x => (f(x - dx) + f(x) + f(x + dx)) / 3;
}
function n_fold_smooth(f, n) {
    return repeated(smooth, n)(f);
}
```</solution></exercise> <exercise>We saw in section <ref name="sec:proc-general-methods">[1.3.3](21)</ref> that attempting to compute square roots by naively finding a fixed point of ![y%5Cmapsto%20x%2Fy](img/0c8a358f0ea7528545e64afdb5977df8.jpg) does not converge, and that this can be fixed by average damping. The same method works for finding cube roots as fixed points of the average-damped ![y%5Cmapsto%20x%2Fy%5E2](img/c758099a62e3fe937262edf777df4e1e.jpg). Unfortunately, the process does not work for fourth roots—a single average damp is not enough to make a fixed-point search for ![y%5Cmapsto%0A%20%20%20%20x%2Fy%5E3](img/472f38ec8ff6c44293fa61ccbe70acd3.jpg) converge. On the other hand, if we average damp twice (i.e., use the average damp of the average damp of ![y%5Cmapsto%20x%2Fy%5E3](img/ea01dc92fba312c2a38bd20cf23e7f06.jpg)) the fixed-point search does converge. Do some experiments to determine how many average damps are required to compute ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th roots as a fixed-point search based upon repeated average damping of ![y%5Cmapsto%0A%20%20%20%20x%2Fy%5E%7Bn-1%7D](img/69ff5294de433ed1f2982dbd3ae376cc.jpg). Use this to implement a simple function for computing ![n](img/493731e423d5db62086d0b8705dda0c8.jpg)th roots using `fixed_point`, `average_damp`, and the `repeated` function of exercise <ref name="ex:repeated">[1.43](22#ex_1.43)</ref>. Assume that any arithmetic operations you need are available as primitives.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_6_div">Solution</button> <solution>```js
function nth_root(n, x) {
    return fixed_point(repeated(average_damp, 
                                math_floor(math_log2(n)))
                       (y => x / fast_expt(y, n - 1)),
                       1.0);
}
```</solution></exercise> <exercise>Several of the numerical methods described in this chapter are instances of an extremely general computational strategy known as _iterative improvement_. Iterative improvement says that, to compute something, we start with an initial guess for the answer, test if the guess is good enough, and otherwise improve the guess and continue the process using the improved guess as the new guess. Write a function `iterative_improve` that takes two functions as arguments: a method for telling whether a guess is good enough and a method for improving a guess. The function `iterative_improve` should return as its value a function that takes a guess as argument and keeps improving the guess until it is good enough. Rewrite the `sqrt` function of section <ref name="sec:sqrt">[1.1.7](9)</ref> and the `fixed_point` function of section <ref name="sec:proc-general-methods">[1.3.3](21)</ref> in terms of `iterative_improve` .<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_22_7_div">Solution</button> <solution>```js
function iterative_improve(good_enough, improve) {
    function iterate(guess) {
        return good_enough(guess)
               ? guess
               : iterate(improve(guess));
    }
    return iterate;
}
```

```js
function sqrt(x) {
    return iterative_improve(y => good_enough(y, x), 
                             y => improve(y, x))(1);
}

sqrt(49);
```

```js
const tolerance = 0.00001;	  
function fixed_point(f, first_guess) {
    return iterative_improve(
               guess => abs(guess - f(guess)) < tolerance,
               f)
           (first_guess);
}

fixed_point(math_cos, 1.0);
```</solution></exercise> 

* * *

[[1]](22#footnote-link-1) Observe that this is a combination whose operator is itself a combination. Exercise <ref name="ex:a-plus-abs-b">[1.4](8#ex_1.4)</ref> already demonstrated the ability to form such combinations, but that was only a toy example. Here we begin to see the real need for such combinations—when applying a function that is obtained as the value returned by a higher-order function.

[[2]](22#footnote-link-2) See exercise <ref name="ex:nth-roots">[1.45](22#ex_1.45)</ref> for a further generalization.

[[3]](22#footnote-link-3) Elementary calculus books usually describe Newton's method in terms of the sequence of approximations ![x_%7Bn%2B1%7D%3Dx_n-g%28x_n%29%2FDg%28x_n%29](img/9096f5d40c02db49e5a53a097ab445fc.jpg). Having language for talking about processes and using the idea of fixed points simplifies the description of the method.

[[4]](22#footnote-link-4) Newton's method does not always converge to an answer, but it can be shown that in favorable cases each iteration doubles the number-of-digits accuracy of the approximation to the solution. In such cases, Newton's method will converge much more rapidly than the half-interval method.

[[5]](22#footnote-link-5) For finding square roots, Newton's method converges rapidly to the correct solution from any starting point.

[[6]](22#footnote-link-6) The notion of first-class status of programming-language elements is due to the British computer scientist Christopher Strachey (1916–1975).

[[7]](22#footnote-link-7) We'll see examples of this after we introduce data structures in chapter 2.

[[8]](22#footnote-link-8) The major implementation cost of first-class functions is that allowing functions to be returned as values requires reserving storage for a function's free names even while the function is not executing. In the JavaScript implementation we will study in section <ref name="sec:mc-eval">[4.1](73)</ref>, these names are stored in the function 's environment.

