以功能的方式在javascript中"组合"功能?

use*_*883 3 javascript functional-programming

我正在学习函数式编程,我想知道是否有办法"组合"这样的函数:

function triple(x) {
    return x * 3;
}
function plusOne(x) {
    return x + 1;
}
function isZero(x) {
    return x === 0;
}
combine(1); //1
combine(triple)(triple)(plusOne)(1); // 10
combine(plusOne)(triple)(isZero)(-1); // true
Run Code Online (Sandbox Code Playgroud)

如果para是一个函数,它将函数"组合"到自身中,如果不是,它将返回最终结果.谢谢!

Tha*_*you 30

这个概念来自可爱的数学.它被称为功能组合.

      f(x) = y
      g(y) = z

   g(f(x)) = z

  (g•f)(x) = z
Run Code Online (Sandbox Code Playgroud)

最后一行是"g等于x的f".什么是伟大的关于组成的功能是消除.请注意g(f(x)) = z我们接受x输入并获得z输出.这完全省略了中间体y.在这里我们说我们删除了点y.

所以我们有一个组合函数(g•f)(x) = z,让我们设置它h(x).

h(x) = (g•f)(x) = z
Run Code Online (Sandbox Code Playgroud)

由于(g•f)已经是一个函数,我们可以删除另一个点x

h(x) = (g•f)(x) = z
   h = (g•f)
Run Code Online (Sandbox Code Playgroud)

函数组合是创建高阶函数和保持代码良好和干净的好方法.我们很容易理解为什么我们希望在您的Javascript中使用它.

"让我们自己创作作曲!"

好的,你听起来很兴奋.开始了...

function triple(x) {
    return x * 3;
}

function plusOne(x) {
    return x + 1;
}

function id(x) { return x; }

function compN(funcs) {
  return funcs.reduce(function(g, f) {
    return function(x) {
      return g(f(x));
    }
  }, id);
}


var g = compN([triple, triple, plusOne]);
// g(x) = (triple • triple • plusOne)(x)

g(1);
//=> 18
Run Code Online (Sandbox Code Playgroud)

评估

triple(triple(plusOne(1)))
triple(triple(2))
triple(6)
18
Run Code Online (Sandbox Code Playgroud)

如果你对这个答案感到满意,那就好了.对于那些想要探索构建更好的高阶函数的人,请继续!

采用这种从其他函数构建高阶函数的概念,我们可以重构和扩展我们的定义 compN

首先让我们了解如何compN评估事物.假设我们要编写3个函数,a,bc

//  g(x) = a(b(c(x)));
//  g(x) = (a•b•c)(x)
//  g    = (a•b•c)
var g = compN([a,b,c])
Run Code Online (Sandbox Code Playgroud)

现在,compN将调用reduce数组并使用我们的id函数初始化它(原因显而易见)

减少工作的方式是它将在我们的数组中的每个项目调用此内部函数一次

function(   g,   f) {   return function(x) { g(f(x)); }; }
Run Code Online (Sandbox Code Playgroud)

我故意添加空格以使其与下表对齐

iteration   g     f     return              explanation
----------------------------------------------------------------------------
#1          id    a     ?x => g(f(x))       original return value
                        ?x => id(a(x))      substitute for `g` and `f`
                        a'                  we'll call this "a prime"

#2          a'    b     ?x => g(f(x))       original return value
                        ?x => a'(b(x))      substitute for `g` and `f`
                        b'                  we'll call this "b prime"

#3          b'    c     ?x => g(f(x))       original return value
                        ?x => b'(c(x))      substitute for `g` and `f`
                        c'                  we'll call this "c prime"
                                            >> c' is the final return value <<
Run Code Online (Sandbox Code Playgroud)

因此,当我们调用时compN([a,b,c]),c'是返回给我们的函数.

"那么当我们用一个争论称这个函数时会发生什么,比如c'(5)?"

alias           original          x         return
----------------------------------------------------------------------------
c'(5);          ?x => b'(c(x))    5         b'(c(5))
b'(c(5))        ?x => a'(b(x))    c(5)      a'(b(c(5)))
a'(b(c(5)))     ?x => id(a(x))    b(c(5))   id(a(b(c(5))))  <-- final return value
Run Code Online (Sandbox Code Playgroud)

换句话说......

compN([a,b,c])(5) === id(a(b(c(5))));
Run Code Online (Sandbox Code Playgroud)

"这很甜蜜,但这对我的大脑来说有点困难."

好的,我同意,如果你像我一样,当我在3个月内回到这个代码时,我会想知道它到底是做什么的.

所以要开始改进compN,让我们先复活一下

//  g(x) = a(b(c(x)));
//  g(x) = (a•b•c)(x)
//  g    = (a•b•c)
var g = compN([a,b,c])
Run Code Online (Sandbox Code Playgroud)

如果我们看另一个例子,也许我们会得到一个暗示

[1,2,3].reduce(function(x,y) { return x + y; }, 0);
//=> 6
Run Code Online (Sandbox Code Playgroud)

是相同的

((0 + 1) + 2) + 3
//=> 6
Run Code Online (Sandbox Code Playgroud)

隐藏在中间的reduce就是这个功能

function (x,y) { return x + y; }
Run Code Online (Sandbox Code Playgroud)

看见?这看起来像一个非常基本的功能,不是吗?并且可以重复使用!如果你认为这add是一个好名字,那你就是对的!让我们看看再次减少

function add(x,y) { return x + y; }
[1,2,3].reduce(add, 0);
//=> 6
Run Code Online (Sandbox Code Playgroud)

这也非常容易理解.我可以随时回到那个代码,并确切知道发生什么.

我知道你在想什么

1 + 2 + 3
Run Code Online (Sandbox Code Playgroud)

看起来非常像

a • b • c
Run Code Online (Sandbox Code Playgroud)

"也许如果我们从compN中提取reduce迭代器,我们可以简化compN的定义......"

这是我们的原创 compN

// original
function compN(fs) {
  return fs.reduce(function(g, f) {
    return function(x) {
      return g(f(x));
    }
  }, id);
}
Run Code Online (Sandbox Code Playgroud)

让我们取出迭代器并调用它 comp

function comp(g, f) {
  return function(x) {
    return g(f(x));
  }
}

var g = comp(triple)(plusOne);    // ?x => triple(plusOne(x))
g(1);                             //       triple(plusOne(1))
//=> 6
Run Code Online (Sandbox Code Playgroud)

好的,让我们看看compN现在修改过的

// revision 1
function compN(fs) {
  return fs.reduce(comp, id);
}
Run Code Online (Sandbox Code Playgroud)

"粗略的改进!所以我们现在都完成了吗?"

哈哈哈哈,没有.

看看那reduce只是坐在那里.这是一个非常有用的功能,我很确定我们可以在很多地方使用它

function reduce(f, i) {
  return function(xs) {
    return xs.reduce(f, i);
  }
}

reduce(add, 0)([1,2,3]);     //=> 6
reduce(comp, id)([a, b, c]); //=> ?x => id(a(b(c(x))))
Run Code Online (Sandbox Code Playgroud)

最后一行应该是对我们compN函数的下一次修订的明显提示

// revision 2
function compN(fs) {
  return reduce(comp, id)(fs);
}
Run Code Online (Sandbox Code Playgroud)

"这看起来并不比修订版1好......"

呃,我知道!但是你肯定会看到(fs)每条线末端的悬空,对吧?

你不会写这个,对吗?

// useless wrapper
function max(x) {
  return Math.max(x);
}

max(3,1);
//=> 3
Run Code Online (Sandbox Code Playgroud)

咄!那是一样的

var max = Math.max;
max(3,1);
//=> 3
Run Code Online (Sandbox Code Playgroud)

所以我提出最后的决定......

// recap
function reduce(f, i) {
  return function(xs) {
    return xs.reduce(f, i);
  };
}

function id(x) { return x; }

function comp(g, f) {
  return function(x) {
    return g(f(x));
  };
}

// revision 3
var compN = reduce(comp, id);
Run Code Online (Sandbox Code Playgroud)

"那仍然以同样的方式运作?"

哎呀它确实如此!

function triple(x) {
    return x * 3;
}

function plusOne(x) {
    return x + 1;
}

var g = compN([triple, triple, plusOne]); // ?x => id(triple(triple(plusOne(x))))
g(1);                                     //       id(triple(triple(plusOne(1))))
//=> 18
Run Code Online (Sandbox Code Playgroud)

"但为什么这样更好?"

嗯,这很简单.当然我们有更多的代码,但我们现在有4个可重用的函数而不是1个.每个功能都有一个简单的任务,很容易立即识别.与原始函数相比,我们最终得到的代码比命令式更具说明性.这在下面进一步强调......


现在真的很酷.我不会在这里深入探讨,但ES6让我们感到惊讶.

// identical functionality as above
let id = x => x;
let reduce = (f,i) => xs => xs.reduce(f,i);
let comp = (g,f) => x => g(f(x));
let compN = reduce(comp, id);

// your functions
let triple = x => x * 3;
let plusOne = x => x + 1;

// try it out!
let g = compN([triple, triple, plusOne]);
console.log(g(1));

//=> 18
Run Code Online (Sandbox Code Playgroud)

继续将其粘贴到Babel REPL中以查看它是否有效

这就是全部,伙计们!

  • 优秀!我花了很长时间阅读它,它对我有很大帮助。感谢您的杰出工作! (2认同)