浅谈函数式编程柯里化的魔法

作者: yfgeek | 来源:发表于2017-02-12 11:48 被阅读618次

    百度百科里对柯里化的定义是

    在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。

    简直看不懂哦,Talk is Cheap,我们拿一个简单的haskell程序说明什么叫

    自动柯里化

    两数相加,返回结果:

    fun add x y = x + y
    

    这是一个典型的柯里化的版本,当我输入 (add 1) 2 时与add 1 2 的返回结果一模一样,都是3

    Prelude> (add 1) 2
    3
    

    但是,换个角度上讲,我明明设置了两个“变量”,在haskell里却把它们的结果又封装成了一个函数,这就是haskell的强大特性:自动柯里化,haskell全部帮你自动处理了。

    简单来说,柯里化指的是函数里只接收一个余下的参数,再调用原函数,返回出相应的结果。

    而同样的,没有柯里化的普通版本却是这样的:

    fun add(x, y) = x + y
    

    这个版本里,只能通过add(1,2)进行相加操作。

    柯里化实际上是将高阶函数进行降阶处理,没理解?接着看~

    JavaScript的柯里化

    JavaScript存在闭包的特性,能够很好的为我们将函数柯里化,但是很可惜JavaScript不支持自动柯里化,这意味着,我们要自己写柯里化的过程,不过还好,这能有效的让我们理解,到底什么是柯里化。

    同样的,还是那个add的简单函数,用JavaScript实现如下:

    function add(x, y){
        if(y===undefined){
            return (z) => x + z;
        }else{
            return x+y;
        }
    }
    

    测试一下!通过执行add(1)(2),果然返回了3,是的,这是一个手动构造柯里化函数的过程。对于大多数函数,我们有通用的柯里化方法:

    function curry(func) {
      var fixedArgs = [].slice.call(arguments,1);
      return function() {
        args = fixedArgs.concat([].slice.call(arguments))
        return func.apply(null, args);
      };
    }
    

    也就是说,对于add函数来说,我们用一个函数生成了另一个范围更小的函数,一层接一层的解决问题。这符合函数式编程的思想,就是我们更关注函数函数之间的关系,而非状态本身,更在乎局部而非整体。

    这个例子体现不出这种思想的精妙之处,那么我们再举一个例子🌰

    如果,我们想让一个已知的数组每个值都做加1操作,传统的方法该怎么做?

    let ar = [1,2,3];
    ar.map((x)=> x+1);
    

    对吧?那么我们下面可以用柯里化的思想把这个问题实现:

    const add = ar => ar.map(x => x + 1);
    add([1, 2, 3]);
    

    首先我们在add里定义了一个函数,并且传入参数为arar进行Array.prototype.map()操作,对每一个数进行遍历,并且返回这个数字+1后的值给ar,这看起来挺玄乎的,但确实可行,我们只关心局部,请试图把add当做数学里中的函数去思考理解,或许会好懂的多。可是这只是方便了我们理解,那么到底有什么好处呢?

    再举一个例子🌰

    如果我们想写一个函数,用来对已知数组的已知量进行相加n的操作,怎么写?传统方法

    let ar= [1,2,3];
    function addToArray(ar,n){
      return ar.map((x)=> x+n)
    };
    console.log(addToArray(ar,1));
    

    但是,我们可以这样写:

    const add = a => b => a+b;
    const addToArray = (ar,n) => ar.map(add(n));
    addToArray([2,3],1)
    

    结果都一样,其实过程是不一样的,你可能真的憋不住了,到底有什么用呢?

    柯里化的好处

    就像上面的例子一样,你能看出不同吗?对,第一个的实质是新写了一个函数,对数组进行map操作,而第二个则是在直接在map里进行add操作,这很通用,一旦你写了很多类似于add的函数,就会方便起来,可以让我们方便的在map,reduce,filter里调用。

    • 能够进行延迟计算,就像add(1)(2)一样,1比2先传入,2就会被延迟计算,在特定的场景里,有一定的应用意义。
    • 当你发现你要调用一个函数,并且调用参数都是一样的情况下,这个参数就可以被柯里化,以便更好的完成任务。
    • 优雅的写法,允许你写出来的代码更干净、更有表达力。

    虽然柯里化在日常应用不多,但仍然是一个不可不了解的好概念。

    相关阅读

    JavaScript柯里化

    Currying and ES6 Arrow Functions

    浅析 JavaScript 中的 函数 currying 柯里化

    Currying - Part 6 of Functional Programming in JavaScript(需梯子)

    相关文章

      网友评论

      • small_a:通用的curry化函数不行吧
        ```js
        function curry(func) {
        var fixedArgs = [].slice.call(arguments,1);
        return function() {
        args = fixedArgs.concat([].slice.call(arguments))
        return func.apply(null, args);
        };
        }
        ```
        这样的curry化只能延迟一次,第二次调用函数就会执行了。
        我自己研究了以下,应该写成这样吧。。。
        ```
        function curry(func){
        var args = [].slice.call(arguments,1);
        var _func = function(){
        [].push.apply(args,arguments);
        return _func;
        }
        _func.end = function(){
        return func.apply(this,args.concat([].slice.call(arguments)));
        }
        return _func;
        }
        ```
        只要不执行返回值的.end()方法,就不会输出,可以一直延迟。。或者把参数长度为0作为执行的标准是不是合理一点?
        yfgeek:不错,感谢建议

      本文标题:浅谈函数式编程柯里化的魔法

      本文链接:https://www.haomeiwen.com/subject/fkqmbttx.html