zoukankan      html  css  js  c++  java
  • Currying 及应用

    Currying,中文多翻译为柯里化,感觉这个音译还没有达到类似 Humor 之于幽默的传神地步,后面直接使用 Currying。

    什么是 Currying

    Currying 是这么一种机制,它将一个接收多个参数的函数,拆分成多个接收单个参数的函数。

    考察下面的代码:

    function add (a, b) {
      return a + b;
    }
    

    add(3, 4); // returns 7

    add 接收两个参数 ab,并返回它们的和 a+b

    经过 curry 化处理后,函数成了如下形式:

    function add (a) {
      return function (b) {
        return a + b;
      }
    }

    现在 add 接收一个参数 a,返回另一个接收一个参数 b 的函数。

    add(3)(4);
    

    var add3 = add(3);

    add3(4);

    现在当调用 add(3) 后,得到的不是和,而是另一个接收一个参数的函数,因此,add 的返回可以继续被调用,add(3)(4) 后面的这次调用才会将 4 加到 3 上得到和。

    var add3 = add(3) 这样的单次调用,得到的函数效果相当于是将 3 保存在了新函数的闭包中,该函数会对传入的参数加 3。

    注意这里提到了将入参 3 保存 到了闭包中后续使用,很容易联想到 Function.prototype.bind(),它就可以对传入的函数提前绑定一些预设的入参:

    function.bind(thisArg[, arg1[, arg2[, ...]]])

    后面会看到,正因为 bind 和 Currying 有点关系,在实现任意函数的 Currying 化时会用到它。

    注意到 Currying 化的定义,其实是将多个参数打散到多个函数中,这个过程可通过代码来自动化,以达到将任意多入参函数进行 Currying 化的目的,后面讨论实现。

    偏函数/Partial Application

    区别与 Currying,如果在拆分入参的过程中,这些拆分出来的函数不是一次只应用其中的一个,而是任意多个,则这些函数就是部分应用(Parital application)了原函数中的入参,称作偏函数。

    考察下面的 add 函数,其实是将前面示例中的 add 入参进行了扩充,由两个增加到四个:

    function add(a, b, c, d) {
      return a + b + c + d;
    }

    那么如下的函数就都是偏函数,它们都部分应用了 add 的入参:

    function partial1(a) {
      return function(c) {
        return a + b + c + d;
      };
    }
    function partial2(a, b) {
      return function(c, d) {
        return a + b + c + d;
      };
    }
    function partial3(a, b, c) {
      return function(d) {
        return a + b + c + d;
      };
    }

    偏函数中这种入参的拆分和部分应用,并不仅限于一层的拆分,可以是任意多次的:

    function partial1(a, b) {
      return function partial2(c) {
        return function partial3(d) {
          return a + b + c + d;
        };
      };
    }
    

    partial1(1)(2, 3)(4); // 10

    其中,partial1partial2partial3 一起构成了原 add 函数的偏函数。

    可以看到,偏函数是 Curring 更加一般(general)的形式,下面看如何实现将任意函数进行 Currying 化,或偏函数化。

    将一般化函数进行 Currying 化

    我们需要构造这么一个函数假设名叫 curry

    function curry(fn){
      // 待实现
    }

    调用 curry 后,我们可以得到原函数 Curry 化后的版本,

    function add (a, b) {
      return a + b;
    }
    

    var currified = curry(add);

    即上述 currified 应该等效为:

    function currified (a) {
      return function (b) {
        return a + b;
      }
    }

    首先,通过 Function.length 是可以知道一个给定函数其预期的入参个数的。

    再加上前面提到的 bind 函数,可以得到如下的实现:

    function curry(f) {
      return function currify() {
        const args = Array.prototype.slice.call(arguments);
        return args.length >= f.length ?
          f.apply(null, args) :
          currify.bind(null, ...args)
      }
    }

    下面测试一下:

    function add(a, b) {
      return a + b;
    }
    

    var currified = curry(add);

    currified(1)(2); // 3

    并且以上实现不只是简单的 Currying 化,可以是任意数量和任意次数的 parial application:

    function add(a, b, c, d) {
      return a + b + c + d;
    }
    

    var currified = curry(add);

    currified(1)(2)(3)(4); // 10
    currified(1)(2, 3)(4); // 10
    currified(1, 2)(3, 4); // 10

    总之就是各种形状hàn姿势,各种颜色hàn皮肤的组合。

    自动化的 CurryIng 倒是实现了,可说了半天,它具体有什么实用价值。

    函数的组合(function composition)

    我们知道代数里面可以有函数的组合,譬如:

    f(x) = x * x
    g(y) = y + 1
    g(f(x)) = x * x + 1
    

    g(f(2)) = 2 * 2 + 1 = 5

    上述代数表达转成 JavaScript 即:

    function f(x) {
      return x ** 2;
    }
    

    function g(y) {
    return y + 1;
    }

    g(f(2)) // 5

    这里用到了两个函数 fg 联合起来得到一个结果,他们都分别只接收一个入参同时返回一个结果。

    像这样只接收一个入参并返回一个结果的函数,便符合组装的需求,可像上面这样自由组合。通过上面的讨论我们知道,任意函数都可经过 Currying 化处理后变成多个只接收单个入参的函数。这就为函数的组合提供了基础。

    因此我们可以将 fg 的结合形成一个新的函数,这个函数作为对外的接口被调用即可。

    const compose = fn1 => fn2 => input => fn1(fn2(input));

    使用:

    const myFn = compose(f)(g);
    myFn(2); // 5

    像上面的 compose 还不够一般化,他只接收两个函数并对其进行结合,下面来看更加一般化的函数组合,将实现接收任意多个函数。

    const pipe = (...fns) => input => fns.reduce((mem, fn) => fn(mem), input)
    

    const double = x => x 2
    const addOne = x => x + 1
    const square = x => x
    x

    pipe(square, double, addOne)(2)

    上面的 pipe 将对输入依次应用 入参中的各函数,所以取名 pipe 管道流。

    以上,函数的组装。

    相关资源

  • 相关阅读:
    POJ1704 Georgia and Bob
    BZOJ1299 巧克力棒
    IPSec
    GRE协议
    L2TP协议
    AAA及Radius
    网络安全概论
    路由策略与引入
    BGP协议
    路由协议
  • 原文地址:https://www.cnblogs.com/Wayou/p/currying_and_function_compose.html
Copyright © 2011-2022 走看看