
写一段可以无限被重用的代码,对于程序员来说无异于黄粱美梦。代码表达清晰是因为代码要表达需求,代码易于重用……好吧,只是因为你要重复使用它。两者不可得兼,你还能奢求更多么?
柯里化可以帮上忙。
通常, Javascript 函数看起来都像这样:
var add = function(a, b){ return a + b } add(1, 2) //= 3 它接受一些参数,然后有一个返回值。我可以使用过多(多余的参数会被忽略)或过少(会给出奇怪的返回值)的参数调用它:
add(1, 2, 'IGNORE ME') //= 3 add(1) //= NaN 柯里化可以使一个多参数函数转化为一系列单参数函数。比如,柯里化后的加法函数:
var curry = require('curry'); var add = curry(function(a, b){ return a + b }) var add100 = add(100) add100(1) //= 101 柯里化后的多参数函数可以如下调用:
var sum3 = curry(function(a, b, c){ return a + b + c }) sum3(1)(2)(3) //= 6 这样的写法在 Javascript 中可能有点丑,所以柯里化也允许你一次传入都个参数:
var sum3 = curry(function(a, b, c){ return a + b + c }) sum3(1, 2, 3) //= 6 sum3(1)(2, 3) //= 6 sum3(1, 2)(3) //= 6 如果你对于那些经常使用柯里化函数的语言很熟(比如Haskell),可能看不出来这样做会带来什么好处。在我的理解中,主要有以下两点好处:
举一个比较明显的例子;从一个集合中获取所有成员的 id :
var objects = [{ id: 1 }, { id: 2 }, { id: 3 }] objects.map(function(o){ return o.id }) 如果你正在厘清第二行代码的真实逻辑,让我帮你把它择出来吧:
MAP over OBJECTS to get IDS (遍历所有对象获取它们的 ID 值)
从函数定义的形式来看,只是这一行就有很多垃圾代码。让我们将其整理干净:
var get = curry(function(property, object){ return object[property] }) objects.map(get('id')) //= [1, 2, 3] 现在我们可以照着代码讲出真实的逻辑了 - 遍历所有对象获取它们的 ID 值。我们创建的get 方法是一个可部分配置的方法。
如果我们想要重用‘从一组对象中获取 id ’这个功能怎么办?最直接的办法就是:
var getIDs = function(objects){ return objects.map(get('id')) } getIDs(objects) //= [1, 2, 3] 嗯,看起来我们又丢掉了优雅简练,退回到杂乱无章的编码风格了。那我们该怎么办呢?额 - 如果 map 方法可以在还没有数据的时候,用一个函数进行部分配置的话?
var map = curry(function(fn, value){ return value.map(fn) }) var getIDs = map(get('id')) getIDs(objects) //= [1, 2, 3] 看起来,如果我们以柯里化函数作为基础构建模块,我们就可以使用它们很容易得创造出全新的功能函数。更让人兴奋的是,代码看起来更能体现你的业务逻辑。
使用这种方式写代码还有另一个好处,它鼓励函数的使用;而不是类的方法。虽然类的方法是很美好的 允许多态,代码可读性高 但它们并不总适合所有的工作,比如拥有大量异步调用的时候。
下面的例子中,我们会从服务器端获取数据,再将其进行处理。数据形式如下:
{ "user": "hughfdjackson", "posts": [ { "title": "why curry?", "contents": "..." }, { "title": "prototypes: the short(est possible) story", "contents": "..." } ] } 你的任务是得到该用户所有文章的标题。现在开始!
fetchFromServer() .then(JSON.parse) .then(function(data){ return data.posts }) .then(function(posts){ return posts.map(function(post){ return post.title }) }) 好吧,这不公平,是我催得太急了。(我还帮你写了上面这些代码 你也许能更加优雅地解决这个问题,可能我已经跑题了)
由于承诺链( chains of promises )(也许你更喜欢称其为回调函数)基本都是与函数一起使用的,你无法简单直接地遍历数据,直到它先从服务器返回并被(无论视觉上或头脑中的)一团乱麻包裹住。
让我们再次出发,这回我们使用已经定义过的方法:
fetchFromServer() .then(JSON.parse) .then(get('posts')) .then(map(get('title'))) Ok ,很少的逻辑,轻松地表达;如果没有柯里化函数我们是无法如此容易地做到的。
柯里化能给予你一种令人垂涎的表达能力。
我建议你立刻开始使用它。如果你已经熟稔于此,那么你一定会发现它的 API 接口直接好用。如果还没有,那么你应当与你的同事一起好好考虑一下了。
翻译自:Why Curry Helps
1 AngelCriss 2017-03-23 16:34:03 +08:00 以下文字来自 http://www.yinwang.org/blog-cn/2015/04/03/paradigms 函数式语言的“拥护者”们,往往认为这个世界本来应该是“纯”( pure )的,不应该有任何“副作用”。他们把一切的“赋值操作”看成低级弱智的作法。他们很在乎所谓尾递归,类型推导, fold , currying , maybe type 等等。他们以自己能写出使用这些特性的代码为豪。可是殊不知,那些东西其实除了能自我安慰,制造高人一等的幻觉,并不一定能带来真正优秀可靠的代码。 |
2 crashX 2017-03-23 16:44:55 +08:00 感觉没什么用,最新的 swift 都去掉这个特性了。 |
4 darluc OP @AngelCriss 说得很实在,请先欣赏外在的美 |
5 Arrowing 2017-03-23 16:48:15 +08:00 @AngelCriss 好难学,写的代码确实比较简洁,但是久了之后,回头来看,自己都看不懂。需要你十分、非常、绝对精通函数式编程,才能算是成功的。所以这个函数式编程推广得这么艰难,太难入门了。 |
6 FrankFang128 2017-03-23 17:11:54 +08:00 思维负担太重啦 |
7 alamaya 2017-03-23 17:20:38 +08:00 难道所谓简洁、优雅的代码就是写出来让别人看不懂? |
8 Phariel 2017-03-23 17:25:08 +08:00 via Android 这东西要是被滥用也很美,你可能得全局搜索非常多次才能明白一个函数使用姿势如何,也得全局找很多次才能去更改源头。 |
9 Kilerd 2017-03-23 17:44:16 +08:00 关键字 python 偏函数 functools partial |
10 QAPTEAWH 2017-03-23 17:49:59 +08:00 语法糖而已 核心是闭包 |
13 think2011 2017-03-23 18:21:29 +08:00 => = > => => Orz |
14 sagaxu 2017-03-23 21:52:48 +08:00 |
15 skydiver 2017-03-23 22:34:17 +08:00 via Android 柯里化和咖喱是一个词,所以原文说美味是双关,可以翻译过来没有这层意思了 |
17 longear 2017-03-24 00:12:05 +08:00 函数加里化(Currying)和偏函数应用(Partial Application)的比较 http://www.vaikan.com/currying-partial-application/ |