V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
darluc
V2EX  ›  JavaScript

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

  •  1
     
  •   darluc · 2017-03-23 16:07:40 +08:00 · 3995 次点击
    这是一个创建于 2591 天前的主题,其中的信息可能已经有所发展或是发生改变。

    查看原文点击这里

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

    柯里化可以帮上忙。

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

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

    查看原文点击这里

    17 条回复    2017-03-24 00:12:05 +08:00
    AngelCriss
        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 等等。他们以自己能写出使用这些特性的代码为豪。可是殊不知,那些东西其实除了能自我安慰,制造高人一等的幻觉,并不一定能带来真正优秀可靠的代码。
    crashX
        2
    crashX  
       2017-03-23 16:44:55 +08:00
    感觉没什么用,最新的 swift 都去掉这个特性了。
    darluc
        3
    darluc  
    OP
       2017-03-23 16:46:51 +08:00
    @crashX 看起来挺美,尤其是异步嵌套的时候
    darluc
        4
    darluc  
    OP
       2017-03-23 16:47:32 +08:00
    @AngelCriss 说得很实在,请先欣赏外在的美
    Arrowing
        5
    Arrowing  
       2017-03-23 16:48:15 +08:00
    @AngelCriss 好难学,写的代码确实比较简洁,但是久了之后,回头来看,自己都看不懂。需要你十分、非常、绝对精通函数式编程,才能算是成功的。所以这个函数式编程推广得这么艰难,太难入门了。
    FrankFang128
        6
    FrankFang128  
       2017-03-23 17:11:54 +08:00
    思维负担太重啦
    alamaya
        7
    alamaya  
       2017-03-23 17:20:38 +08:00
    难道所谓简洁、优雅的代码就是写出来让别人看不懂?
    Phariel
        8
    Phariel  
       2017-03-23 17:25:08 +08:00 via Android
    这东西要是被滥用也很美,你可能得全局搜索非常多次才能明白一个函数使用姿势如何,也得全局找很多次才能去更改源头。
    Kilerd
        9
    Kilerd  
       2017-03-23 17:44:16 +08:00
    关键字 python 偏函数 functools partial
    QAPTEAWH
        10
    QAPTEAWH  
       2017-03-23 17:49:59 +08:00
    语法糖而已

    核心是闭包
    Swift3030
        11
    Swift3030  
       2017-03-23 18:00:57 +08:00
    @crashX 只是去掉语法糖, curried function 还是有的。
    darluc
        12
    darluc  
    OP
       2017-03-23 18:15:59 +08:00
    @Kilerd 「偏函数」受教!
    think2011
        13
    think2011  
       2017-03-23 18:21:29 +08:00
    => = > => => Orz
    skydiver
        15
    skydiver  
       2017-03-23 22:34:17 +08:00 via Android   ❤️ 1
    柯里化和咖喱是一个词,所以原文说美味是双关,可以翻译过来没有这层意思了
    skydiver
        16
    skydiver  
       2017-03-23 22:34:48 +08:00 via Android
    @skydiver 可以=可惜
    longear
        17
    longear  
       2017-03-24 00:12:05 +08:00   ❤️ 1
    函数加里化(Currying)和偏函数应用(Partial Application)的比较
    http://www.vaikan.com/currying-partial-application/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1083 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:09 · PVG 07:09 · LAX 16:09 · JFK 19:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.