loading...

0

为什么使用柯里化?(译)

javascript读完大概需要8分钟

  • 发布时间:2018-01-21 15:34 星期日
  • 刘伟波
  • 297
  • 更新于2018-01-22 13:28 星期一

编写的代码能被毫不费力地重复使用是程序员的一个白日梦。首先,它是有含义的,因为代码都是根据需求用某种方式所写的;并且,它是可重用的,因为你打算重用它。你还想要什么?

柯里化能帮上忙。

什么是柯里化?它又棒在哪里?

普通的 JavaScript 调用会像这样:

    var add = function(a, b){ return a + b }
    add(1, 2) //= 3

一个函数有多个参数,并有一个返回值。调用的参数可以比定义的少(会出错),或多(被忽视):

    add(1) //= NaN
    add(1, 2, 'IGNORE ME') //= 3

柯里化后的函数是将多个参数由一系列单参数函数表示的函数。比如,柯里化后的加法会是这样:




ES5
function currying(fn) {
var slice = Array.prototype.slice,
__args = slice.call(arguments, 1);
return function () {
var __inargs = slice.call(arguments);
return fn.apply(null, __args.concat(__inargs));
};
}
ES6
function currying (fn,...originArgs){
return function(...args){
return fn.apply(null,[...originArgs,...args])
}
}
    
var curry=require('query');
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

所以?

如果你不习惯柯里化函数是语言的常规部分,那么柯里化可能对你没有什么明显的优势,而在我看来,它有 2 大优势:

  • 程序片段越小越容易被配置
  • 尽可能地函数化

小片段

举个显而易见的例子,获取集合每个成员的 id:

    var objects = [{ id: 1 }, { id: 2 }, { id: 3 }]
    objects.map(function(o){ return o.id })

如果你想知道第二行的逻辑,让我来告诉你:

循环遍历对象来获得 id

就在这行里就有许多不佳的实现,让我带你一个个清楚它。首先,在函数定义中,

    var get = curry(function(property, object){ return object[property] })
    objects.map(get('id')) //= [1, 2, 3]

以上创建的 get 函数是一个可配置的函数。

如果我们想重用获取对象 id 的功能,就可以这样简单地使用它:

    var getIDs = function(objects){
        return objects.map(get('id'))
    }
    getIDs(objects) //= [1, 2, 3]

嗯,这似乎又从优雅简洁回到了呆板。我们还能做些什么?那如果,遍历方法也可以被配置哪?

    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": "..." }
        ]
    }

目标就是获取每个 posts 中的 title。

    fetchFromServer()
        .then(JSON.parse)
        .then(function(data){ return data.posts })
        .then(function(posts){
            return posts.map(function(post){ return post.title })
        })

由于 Promise 链(或者你更倾向于回调)本质上是函数调用,你不能轻易的映射一个从服务器中获取但尚未经过加工的值。这整个无论从视觉(或心理)上都很粗糙。

那试试用刚刚定义好的工具来看看:

    fetchFromServer()
        .then(JSON.parse)
        .then(get('posts'))
        .then(map(get('title')))

如果没有将函数经过柯里化,就无法如此轻易地使代码逻辑变得如此精炼和易于表达。

概括

柯里化会给你写代码带来神奇的力量。

我建议你掌握并熟练地运用它。如果你已经熟悉它的概念,我猜你会对它的 API 很满意,如果不是,你和你的同事应该试着考虑它。





你可能感兴趣的文章

    发表评论

    评论支持markdown,评论内容不能超过500字符,如果内容过多或者要及时回复,建议去 segmentfault平台, 也可以来我的直播间来提问。
    关于技术问题或者有啥不懂的都可以留言, 我会定期回复答疑, 也可以来 我的直播间 提问, 推荐最新仓库 前端知识体系, 感謝支持!