说明: 本文来自SitePoint,作者:M. David Green

文章版权属于原网站/原作者。我依旧只是个搬运工+不称职的翻译。

柯里化(Currying),或称为偏函数应用(Partial Application),是函数式编程中会给听起来给更熟悉传统的JavaScript编程的人带来困惑。但只要使用得当,柯里化会让你的函数式JavaScript更加易读。

更易读以及更具有扩展性

函数式JavaScript的一个显而易见的优点是代码量更少、代码内聚更高、代码更少的重复,在代码行更少的情况下,也能输出正确的结果。在不熟悉函数是编程如何实现之前,有时会多花一些时间去阅读理解代码,代码的书写也会变得难以理解。

如果你对柯里化这个词有影响,但从来不知道它的意思,你完全不用担心你曾经认为它是一种陌生或者神奇的技术。其实柯里化是一个相当简单的概念,当处理函数参数,它能处理一些常见的问题,并且为开发者开辟了更多的选择。

什么是柯里化?

简而言之,柯里化是一种允许偏函数应用作为参数组成新的函数。意味着为了获取函数结果,你可以传递函数所需要的所有参数,或者也可以传递一部分参数得到结果作为其余函数的参数。情况就是这么简单。

柯里化是如Haskell和Scala这样的函数式语言的基础。JavaScript具有函数式编写的能力,但柯里化并不实现于其中(至少在目前版本是这样的)。但我们已经知道一些函数式的方法,能让我们能在JavaScript中使用柯里化。

为了给你展现柯里化是如何实现的,我们写了JavaScript中第一个入门的柯里化函数,使用熟悉的方法创建了柯里化函数。我们想通过名字用来打招呼作为例子。我们知道如何通过创造一个简单的、包含使用名字和处理的打招呼的函数,并使用控制台打印名字。

1
2
3
4
5
var greetCurried = function(greeting) {
return function(name) {
console.log(greeting + ", " + name);
};
};

这个函数需要两个参数–名称和方式才能正常的使用。但是我们可以仅通过简单的嵌套柯里化重写此函数,使之基本函数只需要传递方式,返回一个函数用于接受我们任意传递的名称。

第一次柯里化

1
2
3
4
5
var greetCurried = function(greeting) {
return function(name) {
console.log(greeting + ", " + name);
};
};

这个小小的改变,让我们创建了一个可为任何类型方式的新函数,并且接受我们任意传递的名称。

1
2
3
var greetHello = greetCurried("Hello");
greetHello("Heidi"); //"Hello, Heidi"
greetHello("Eddie"); //"Hello, Eddie"

我们也可以对原始函数进行柯里化调用,只需要使用左到右的方式从传入参数的集合中分离参数。

1
greetCurried("Hi there")("Howard"); //"Hi there, Howard"

为何不在浏览器中尝试一下此代码呢?

此为JS Bin的链接

全部柯里化!

最酷的是,现在我们学习了可以使用这种方式处理参数用于改变传统函数,我们能对任意数量的参数使用此方法:

1
2
3
4
5
6
7
8
9
var greetDeeplyCurried = function(greeting) {
return function(separator) {
return function(emphasis) {
return function(name) {
console.log(greeting + separator + name + emphasis);
};
};
};
};

我们四个参数的函数和两个参数的函数有同样的灵活度。无论嵌套有多深,我们都可以创造出一个包含任意人数的函数来满足我们的预期:

1
2
3
var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?");
greetAwkwardly("Heidi"); //"Hello...Heidi?"
greetAwkwardly("Eddie"); //"Hello...Eddie?"

此为JS Bin的链接

柯里化传统函数

你能看到这样的尝试非常强大,特别是如果你需要创造一堆具体的函数的情况下。唯一的问题在于语法。一旦写出柯里化的函数,你需要不停地嵌套返回函数,调用每个都包含自有参数变量的新函数。这样会变得相当复杂。

为了解决这个问题,创造出一个包含不用包含所有嵌套返回的现存函数名称的快速和脏的柯里化函数。一个柯里化的函数需要放出参数列表,并且使用它们用于返回原始函数的当前版本。

1
2
3
4
5
6
7
8
var curryIt = function(uncurried) {
var parameters = Array.prototype.slice.call(arguments, 1);
return function() {
return uncurried.apply(this, parameters.concat(
Array.prototype.slice.call(arguments, 0)
));
};
};

为了使用这个,我们设定号存放任何参数数量的函数,和我们初始设定的参数一致。然后我们获得了一个包含等待剩余参数的函数。

1
2
3
4
5
6
var greeter = function(greeting, separator, emphasis, name) {
console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."

和之前一样,我们使用从原始函数中分离出没有限制参数的函数。

1
2
3

var greetGoodbye = curryIt(greeter, "Goodbye", ", ");
greetGoodbye(".", "Joe"); //"Goodbye, Joe."

此为JS Bin的链接

开始严肃对待柯里化

我们的小柯里化函数并不包括所有情况,比如没有或者可选参数,但是它做的有意义同样严格按照语法传递参数。

Ramda为代表的一些函数式JavaScript库又更灵活的柯里化方式以至于可以用来打破函数所必须的参数,并且可以让你挨个或者组团传递参数用于生成自定义的柯里化参数。如果你想更广泛的柯里化,这个可能是一种方法。

不管你在自己的程序里如何增加柯里化,无论你想使用嵌套参数或者更喜欢健壮的柯里化函数,为当前的柯里化函数提出一个组合化的名称将会让你的代码更具有可读性。每一个参数都需要一个让其具有明确行为以及期望传递的名字。

参数顺序

一个需要注意的是柯里化时参数的顺序。正如我们的描述,你显然希望参数大多数情况下是一个接着一个传递直到原始函数最后一个。

提早思考参数顺序会让你在工作中更容易的使用柯里化。并且思考参数顺序在设计函数大多数情况下都不是坏习惯。

小结

柯里化在函数式编程中是一项难以置信的有用办法。它允许你生成代码少、易于配置且运行稳定的库,同时易于使用,让你的代码更易理解。在实际代码编写时,实现柯里化会让你更容易的在代码中使用偏函数应用,避免有可能产生的重复,并且能让你在命名和处理函数参数时养成良好习惯。