为什么柯里化有用(下午茶译文赏析)

2017-03-23 16:07:40 +08:00
 darluc

查看原文点击这里

写一段可以无限被重用的代码,对于程序员来说无异于黄粱美梦。代码表达清晰是因为代码要表达需求,代码易于重用……好吧,只是因为你要重复使用它。两者不可得兼,你还能奢求更多么?

柯里化可以帮上忙。

什么是柯里化,为何它如此美味?

通常, 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

查看原文点击这里

4011 次点击
所在节点    JavaScript
17 条回复
AngelCriss
2017-03-23 16:34:03 +08:00
以下文字来自 http://www.yinwang.org/blog-cn/2015/04/03/paradigms

函数式语言的“拥护者”们,往往认为这个世界本来应该是“纯”( pure )的,不应该有任何“副作用”。他们把一切的“赋值操作”看成低级弱智的作法。他们很在乎所谓尾递归,类型推导, fold , currying , maybe type 等等。他们以自己能写出使用这些特性的代码为豪。可是殊不知,那些东西其实除了能自我安慰,制造高人一等的幻觉,并不一定能带来真正优秀可靠的代码。
crashX
2017-03-23 16:44:55 +08:00
感觉没什么用,最新的 swift 都去掉这个特性了。
darluc
2017-03-23 16:46:51 +08:00
@crashX 看起来挺美,尤其是异步嵌套的时候
darluc
2017-03-23 16:47:32 +08:00
@AngelCriss 说得很实在,请先欣赏外在的美
Arrowing
2017-03-23 16:48:15 +08:00
@AngelCriss 好难学,写的代码确实比较简洁,但是久了之后,回头来看,自己都看不懂。需要你十分、非常、绝对精通函数式编程,才能算是成功的。所以这个函数式编程推广得这么艰难,太难入门了。
FrankFang128
2017-03-23 17:11:54 +08:00
思维负担太重啦
alamaya
2017-03-23 17:20:38 +08:00
难道所谓简洁、优雅的代码就是写出来让别人看不懂?
Phariel
2017-03-23 17:25:08 +08:00
这东西要是被滥用也很美,你可能得全局搜索非常多次才能明白一个函数使用姿势如何,也得全局找很多次才能去更改源头。
Kilerd
2017-03-23 17:44:16 +08:00
关键字 python 偏函数 functools partial
QAPTEAWH
2017-03-23 17:49:59 +08:00
语法糖而已

核心是闭包
Swift3030
2017-03-23 18:00:57 +08:00
@crashX 只是去掉语法糖, curried function 还是有的。
darluc
2017-03-23 18:15:59 +08:00
@Kilerd 「偏函数」受教!
think2011
2017-03-23 18:21:29 +08:00
=> = > => => Orz
sagaxu
2017-03-23 21:52:48 +08:00
skydiver
2017-03-23 22:34:17 +08:00
柯里化和咖喱是一个词,所以原文说美味是双关,可以翻译过来没有这层意思了
skydiver
2017-03-23 22:34:48 +08:00
@skydiver 可以=可惜
longear
2017-03-24 00:12:05 +08:00
函数加里化(Currying)和偏函数应用(Partial Application)的比较
http://www.vaikan.com/currying-partial-application/

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/349771

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX