# [译] 柯里化与函数组合

·  阅读 2835

### 什么是柯里化函数？

``````// add = a => b => Number
const add = a => b => a + b;

``````const result = add(2)(3); // => 5

`add` 函数接受一个参数，然后返回自己的 偏函数应用`a` 固定在偏函数应用的闭包作用域中。闭包指函数绑定其语法作用域。闭包在创建函数运行时被创建。固定意味着在闭包绑定的作用域内变量被赋值。

### 什么是无点风格（point-free style）？

``````function foo (/* 这里定义参数*/) {
// ...
}

const foo = (/* 这里定义参数 */) => // ...

const foo = function (/* 这里定义参数 */) {
// ...
}

``````// inc = n => Number
// 把任何数字加一。
const inc = add(1);

inc(3); // => 4

``````const inc10 = add(10);
const inc20 = add(20);

inc10(3); // => 13
inc20(3); // => 23

``````inc(3) // 4

### 为什么要把函数柯里化？

``````f: a -> b
g: b -> c

``````// 代数定义，从 Haskell 借鉴了组合操作符 `.`

h: a -> c
h = f . g = f(g(x))

``````const g = n => n + 1;
const f = n => n * 2;

const h = x => f(g(x));

h(20); //=> 42

``````f . g = f(g(x))

``````const compose = (f, g) => f(g(x));

``````g . f . h

``````const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);

``````const g = n => n + 1;
const f = n => n * 2;

// 使用 `compose(f, g)` 替换 `x => f(g(x))` `
const h = compose(f, g);

h(20); //=> 42

### 跟踪（Trace）

``````const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

``````const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);

const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

/*

*/

const h = compose(
trace('after f'),
f,
trace('after g'),
g
);

h(20);
/*
after g: 21
after f: 42
*/

`compose()` 是非常有用的工具，但当我们需要组合多于两个函数时，从上到下的顺序会更方便我们阅读。我们可以通过反转被调用函数的顺序来做到。这里有另一个名为 `pipe` 的组合工具，它反转了组合的顺序：

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

/*

*/
const h = pipe(
g,
trace('after g'),
f,
trace('after f'),
);

h(20);
/*
after g: 21
after f: 42
*/

### 结合柯里化和函数组合

``````const map = fn => mappable => mappable.map(fn);

const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const log = (...args) => console.log(...args);

const arr = [1, 2, 3, 4];
const isEven = n => n % 2 === 0;

const stripe = n => isEven(n) ? 'dark' : 'light';
const stripeAll = map(stripe);
const striped = stripeAll(arr);
log(striped);
// => ["light", "dark", "light", "dark"]

const double = n => n * 2;
const doubleAll = map(double);
const doubled = doubleAll(arr);
log(doubled);
// => [2, 4, 6, 8]

``````f: a => b
g:      b => c
h: a    =>   c

``````f: a => b
g:     (x, b) => c
h: a    =>   c

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
g,
trace('after g'),
f,
trace('after f'),
);

h(20);
/*
after g: 21
after f: 42
*/

`trace()` 定义两个参数，但是每次只取一个参数，允许我们专用化行内函数。如果 `trace()` 没有被柯里化，就不能这样使用它。我们就必须这样写管道函数：

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = (label, value) => {
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
g,
// trace() 不在是无点风格，并引入 `x` 作为中间变量。
x => trace('after g', x),
f,
x => trace('after f', x),
);

h(20);

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = value => label => {
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
g,
// trace() 不能为无点风格，因为期望的参数顺序错误
x => trace(x)('after g'),
f,
x => trace(x)('after f'),
);

h(20);

``````const flip = fn => a => b => fn(b)(a);

``````const flippedTrace = flip(trace);

``````const flip = fn => a => b => fn(b)(a);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = value => label => {
console.log(`\${ label }: \${ value }`);
return value;
};
const flippedTrace = flip(trace);

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
g,
flippedTrace('after g'),
f,
flippedTrace('after f'),
);

h(20);

``````const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

`trace()` 每次应用 `label` 时会创建专用版本的跟踪函数，它会在管道中用到，管道中 `label``trace` 返回的偏函数应用中是固定的。所以：

``````const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

const traceAfterG = trace('after g');

...等同于：

``````const traceAfterG = value => {
const label = 'after g';
console.log(`\${ label }: \${ value }`);
return value;
};

``````const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = label => value => {
console.log(`\${ label }: \${ value }`);
return value;
};

// 柯里化版本的 trace() 能让我们避免这种代码...
const traceAfterG = value => {
const label = 'after g';
console.log(`\${ label }: \${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

const h = pipe(
g,
traceAfterG,
f,
trace('after f'),
);

h(20);

### 下一步

EricElliottJS.com 的会员可以看到此话题的完全指南视频。会员可以访问 ES6 Curry & Composition 课程

Eric Elliott 是 Programming JavaScript Applications(O’Reilly) 的作者，并且是软件导师制平台 DevAnywhere.io 的合作创始人。他拥有为 Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC 和顶尖音乐艺术家包括 Usher、Frank Ocean、Metallica 等工作的经验。