6. 过程式抽象:Map、Curry 和 Compose¶
6.1. 映射模式¶
抽象 是编程中最强大的工具之一。但它是什么?让我们通过考虑以下两个例子来开始理解这个概念,这两个例子分别称为 addBonusPoint 和 doubleAll 。
前述示例中的这种计算模式称为 映射模式:它接受一个函数和一个列表,并返回将函数应用于输入列表中每个元素所得到的列表。 关于映射模式的问题是以下问题。
6.2. 函数组合¶
我们接下来考虑的一个抽象概念称为函数组合。我们想要一个名为 compose 的函数,它接受两个函数 f 和 g,并返回一个函数,该函数首先将 g 应用于其参数,然后将 f 应用于该结果。换句话说:
注意,函数 \(f \circ g\) 有时被称为"g 之后 f"或"f 跟随 g",因为当这两个函数按此顺序组合时,g 首先被应用,然后才是 f。相比之下,在组合函数 \(g \circ f\) 中,读作"f 之后 g",f 首先被应用,然后才是 g。
以下问题关于函数复合。
6.3. 柯里化¶
在之前开发的 map 函数中:
var map = function (f,ns) { if (fp.isNull(ns))
return [ ];
else
return fp.cons(
f(fp.hd(ns)), map(f, fp.tl(ns))
);
};
我们无法将想要在列表元素上执行的计算,例如“将列表中所有元素翻倍”或“将列表中所有元素加一”,与其参数列表分离,因为 map 函数需要同时拥有这两个参数。
相反,我们希望编写一个函数,即下面的 map1,它只接受一个函数作为参数,例如 doubleIt,并返回另一个函数,在我们的例子中是函数 doubleAll,它可以一般性地应用于所有数字列表。map1 是一个创建函数的函数,而 map 则不是。
var map1 = function (f) {
return function (ns) {
if (fp.isNull(ns))
return [ ];
else
return fp.cons(f(fp.hd(ns)), map1(f)(fp.tl(ns)));
};
};
var doubleAll = map1(doubleIt);
doubleAll( [1,2,3,4,5] );
柯里化 是转换一个接受两个或更多参数(例如 map)的函数,为一个(例如 map1)接受第一个参数并返回另一个接受第二个参数并返回另一个函数的过程,该函数因外层函数定义所创建的闭包而将第一个参数“硬编码”其中。该过程以著名逻辑学家 Haskell Curry 命名。
因此,我们的 map1 函数是我们 map 函数的柯里化版本。
我们将通过编写一个名为 curry 的函数来抽象这种柯里化模式,该函数可以对任意两个参数的函数进行柯里化:
var curry = function (f) {
return function (x) {
return function (y) {
return f(x,y);
};
};
};
现在我们不再需要编写 map1,而是可以让 curry 为我们创建它。
var map1 = curry(map);
作为使用 curry 的另一个例子,考虑以下 fillIn 函数:
虽然上述例子可能显得有些牵强,但柯里化的重要性不可夸大。它允许我们将任意两个参数的函数转换为一个参数的函数,该函数返回一个参数的函数。我们将在第三章讨论 lambda 计算时,再次回到这种操作的重要性。
下一个问题涉及柯里化与函数组合。
6.4. 更多柯里化¶
本节关于过程抽象的最后一个问题将为您提供对函数 curry 和 compose 的密集练习。该问题是随机的,必须连续解决三次。

