译者注:本文翻译自 Hugh FD Jackson 的 《Why Curry Helps》,转载请与原作者或本人联系。下面开始正文。
程序员的梦想是编写代码,并能够非常容易地对其进行复用。还要有强表达力,因为你书写的方式就是在表达你想要的东西,并且它应该被复用,因为… 好吧,你正在复用。你还想要什么呢?
curry 可以帮忙。
什么是柯里化,为什么它如此的美味?
JavaScript 中正常的函数调用如下:
1 | var add = function(a, b){ return a + b } |
一个函数接受多个参数,并返回一个值。我可以使用少于指定数量的参数调用它(可能得到奇怪的结果),或者多于指定的数量(超出的部分一般会被忽略)。
1 | add(1, 2, 'IGNORE ME') //= 3 |
一个柯里化的函数需要借用一系列单参数函数来处理它的多个参数。例如,柯里化的加法会是这样:
1 | var curry = require('curry') |
接受多个参数的柯里化函数将被写成如下形式:
1 | var sum3 = curry(function(a, b, c){ return a + b + c }) |
由于这在 JavaScript 语法中很丑,curry 允许你一次调用多个参数:
1 | var sum3 = curry(function(a, b, c){ return a + b + c }) |
所以呢?
如果你还未习惯这样一门语言:柯里化函数是其日常工作一部分(如 Haskell),那么它给我们带来的好处可能不太明显。在我看来,有两点非常重要:
- 小的模块可以轻松地配置和复用,不杂乱。
- 从头至尾都使用函数。
小模块
我们来看一个明显的例子;映射一个集合来获取它的成员的 ids:
1 | var objects = [{ id: 1 }, { id: 2 }, { id: 3 }] |
如果你正想搞清楚第二行的真正逻辑,我来跟你解释一下吧:
MAP over OBJECTS to get IDS (对Objects进行映射,来获得对应的ID)
有很多种实现这种操作的方式;可以函数定义的形式实现。我们来理一理:
1 | var get = curry(function(property, object){ return object[property] }) |
现在我们正在探讨这个操作的真正逻辑 - 映射这些对象,获取它们的 ids 。BAM。我们在 get
函数中真正创建的是一个 可以部分配置的函数。
如果想复用 ‘从对象列表中获取ids’ 这个功能,该怎么办呢?我们先用一种笨的方法实现:
1 | var getIDs = function(objects){ |
Hrm,我们似乎从高雅和简洁的方式回到了混乱的方式。可以做些什么呢?Ah,如果 map
可以先部分配置一个函数,而不同时调用集合,会怎样呢?
1 | var map = curry(function(fn, value){ return value.map(fn) }) |
我们开始看到,如果基本的构建块是柯里化函数,我们可以轻松地从中创建新的功能。更令人兴奋的是,代码读起来也很像你所工作领域(语言、环境)的逻辑。
全是函数
这种方法的另一个优点是它鼓励创建函数,而不是方法。虽然方法很好 - 允许多态,可读性也不错 - 但它们并不总是能拿来干活的工具,比如大量的异步代码。
在这个示例中,我们从服务器获取一些数据,并对其进行处理。数据看起来像是这样:
1 | { |
你的任务是提取每个用户的帖子的标签。赶紧来试一下:
1 | fetchFromServer() |
好吧,这不公平,你在催我。(另外,我代表你写了这段代码 - 可能你会更有优雅地解决它,但我好像离题了…)。
由于 Promises 链(或者,如果你喜欢,也可以用回调)需要与函数一起 工作,你不能轻易地映射从服务器获取的值,而无需首先显式地将其包裹在代码块中。(需要显式的写出参数)
再来一次,这次使用已经定义好的工具:
1 | fetchFromServer() |
这具有很强的逻辑性、表达力;如果不使用柯里化函数,我们几乎不可能轻易的将其实现。
总结(tl;dr)
curry 赋予你一种强大的表达能力。
我建议你下载下来,玩一会儿。如果你已经熟悉了这个概念,我觉得你可以直接找到合适的 API。如果没有的话,建议你和你的同事一起研究一下吧。