• 为你的 Javascript 加点咖喱


    咖喱,是一种由多种香料调配而成的调料,常见于印度菜、泰国菜和日本菜等 ( by the way, 咖喱鸡块我的爱 ),一般伴随肉类和饭一起吃。关于咖喱的介绍详见维基百科: http://zh.wikipedia.org/wiki/%E5%92%96%E5%93%A9 。

    当然,这篇文章不是一篇食谱,以在下的厨艺,目前只能停留在吃咖喱的阶段。咖喱的英文写作 Curry ,这个词还有另一层含义,为一种基于 Haskell 的实验式的函数编程语言,混合了 函数 与 逻辑编程 ,也加入了 约束编程 的特性。取名于数学家 Haskell Curry 。不得不说这位数学家对计算机的影响还真是很大。

    However, 这里要谈的不是这位伟大的数学家,也不是 Haskell 语言,下面我先描述一个场景。

    Javascript 作为一种函数式的编程语言,函数的使用称之为应用 ( applied ),有一定 JS 编程经验的开发者很快能想到内置的 Function.prototype.apply() 方法,在应用函数的时候,我们可以这样写:

    1 var sayHello = function(param) {
    2     return 'Hello, ' + param;        
    3 }
    4 
    5 sayHello.apply(null, ['Tom']);      // 'Hello, Tom'

    sayHello() 内部的 this 指向全局对象。或者也可以让 JS 为我们完成数学运算:

    1 function add(x, y) {
    2     return x + y;
    3 }
    4 
    5 add.apply(null, [1, 2]);    // 3

    这里应用函数的时候,我们一次性将所有的参数都传了进去,然而在实际应用中,我们可能会遇到不需要一次传入所有参数的情况,若 add() 函数的写法不变的话,只传入一个参数的时候就会产生错误,这不是我们希望看到的。

    我们希望看到什么呢,在这里我们先 YY 一下:

    1 var add = function(x, y) {
    2     return x + y;
    3 }
    4 
    5 // 部分应用
    6 var partialAdd = add.partialApply(null, [1]);
    7 partialAdd.apply(null, [2]);    // 3

    部分应用的步骤为我们提供了一个可供调用的新函数,随后我们可以使用其他参数来调用这个新函数。看起来很美吗?可惜的是,Javascript 中并没有 partialApply() 这样的方法和函数。不过前端开发者都是有进取精神的小强,再加上 JS 是一门灵活性很强的语言,我们完全可以构造出这样的部分应用函数,来满足我们的需求。

    扯了这些,和之前提到的咖喱有关系吗?有的,这种是函数理解并处理部分应用场景的过程我们称之为 Curry 过程(也叫做函数的柯里化)。(别喷我,写一篇能让人有兴趣看下去的文章好难,不写点吸引人眼球的东西没人看啊)

    回到之前所说,对于一个简短的加法运算,比如 1 + 2,部分应用的实现思路可以写成 add(1)(2),这就要求我们在调用 add(1) 后不仅不会产生错误,更要返回一个能够继续传入第二个参数的函数,说到这里答案已经呼之欲出了:

     1 function add(x, y) {
     2     // 部分
     3     if (typeof y === 'undefined') {
     4         return function(y) {
     5             return x + y;
     6         }
     7     }
     8 
     9     // 完全
    10     return x + y;
    11 }

    其实这是一种常见的函数变换技巧,称为函数的不完全调用 (partial application)。这种函数变换的特点是每次调用都返回一个参数,直到得到最终运行结果为止。不过我们能用一种更为通用的方法来处理相同的任务吗,答案自然是肯定的:

    function curry(fn) {
        var slice = Array.prototype.slice,
            stored_args = slice.call(arguments, 1);
        return function() {
            var new_args = slice.call(arguments),
                args = stored_args.concat(new_args);
            return fn.apply(null, args);
        }
    }

    这样,在返回的函数中便保存了已经传入的参数,保存在变量 a 中,在返回的函数开头,剥离了第一个参数,因为这个参数是即将被 curry 化的函数,同时也保存了指向 slice() 方法的私有引用。当我们访问返回的函数时,新函数将原有的部分应用参数合并到新参数,再将合并后的参数应用到原始的函数中。

    这样,使任意函数 curry 化的通用方法就有了,可将之前定义的 add() 函数用来测试。

    // 普通函数
    function add(x, y) {
        return x + y;
    }
            
    // 将一个函数 curry 化以获得一个新的函数
    var newadd = curry(add, 1);
    newadd(2);
            
    // 另一种方法
    curry(add, 3)(4);
            
    // 连续 curry 化
    var newadd = curry(add, 1);
    var anothernewadd = curry(newadd, 2);

    实际上,这些只是抛砖引玉,curry 化可改进的地方还有很多,比如当对参数的类型和顺序有要求时如何根据实际情况编写适合的 curry 化函数等。后续我会对内容作更多的补充,也欢迎各位畅所欲言,多提意见。

  • 相关阅读:
    HDU6301 SET集合的应用 贪心
    线段树与树状数组的对比应用
    树状数组
    JDBC链接MySQL数据库
    HDU4686Arc of Dream 矩阵快速幂
    HDU1757矩阵快速幂
    B1013. 数素数 (20)
    B1023. 组个最小数 (20)
    [教材]B1020. 月饼 (25)
    [教材]A1025. PAT Ranking (25)
  • 原文地址:https://www.cnblogs.com/classicemi/p/3292416.html
Copyright © 2020-2023  润新知