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

时间:2015-05-12 17:52:15

标签: 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

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

5 个答案:

答案 0 :(得分:28)

这个概念来自可爱的数学。它被称为function composition

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

   g(f(x)) = z

  (g•f)(x) = z

最后一行是" g的x等于z" 撰写函数的优点是消除 points 。请注意g(f(x)) = z,我们接受x输入并获得z输出。这完全省略了中间y。我们在这里说删除了点y

所以我们有一个撰写函数(g•f)(x) = z,让它设置为h(x)

h(x) = (g•f)(x) = z

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

h(x) = (g•f)(x) = z
   h = (g•f)

函数组合是创建higher-order functions并使代码保持良好和干净的好方法。很容易理解我们为什么在您的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

评价

triple(triple(plusOne(1)))
triple(triple(2))
triple(6)
18
  

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

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

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

//  g(x) = a(b(c(x)));
//  g(x) = (a•b•c)(x)
//  g    = (a•b•c)
var g = compN([a,b,c])

现在,compN会在数组上调用reduce并使用我们的id函数对其进行初始化(原因显而易见)

reduce 的工作原理是它会在我们的数组中每个项目调用一次内部函数

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

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

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 <<

因此,当我们致电compN([a,b,c])时,c'是返回给我们的函数。

&#34;当我们使用争论调用该函数时会发生什么,例如c'(5)?&#34;

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

换句话说......

compN([a,b,c])(5) === id(a(b(c(5))));

&#34;那甜言蜜语,但这让我的大脑难以理解。&#34;

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

所以要开始改进compN,请让我们先修复

//  g(x) = a(b(c(x)));
//  g(x) = (a•b•c)(x)
//  g    = (a•b•c)
var g = compN([a,b,c])

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

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

相同
((0 + 1) + 2) + 3
//=> 6

隐藏在reduce中间的是此功能

function (x,y) { return x + y; }

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

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

那个 super 也很容易理解。我可以随时回到那段代码,并且知道完全正在进行什么。

我知道你在想什么

1 + 2 + 3

看起来非常像

a • b • c

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

这是我们原来的compN

// original
function compN(fs) {
  return fs.reduce(function(g, f) {
    return function(x) {
      return g(f(x));
    }
  }, id);
}

让我们取出迭代器并将其称为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

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

// revision 1
function compN(fs) {
  return fs.reduce(comp, id);
}

&#34; Gnarly改进!那么我们现在全部完成了吗?&#34;

哈哈哈哈,没有。

看看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))))

最后一行应该是我们compN函数

的下一次修订的明显提示
// revision 2
function compN(fs) {
  return reduce(comp, id)(fs);
}

&#34;看起来并不比修订版1好......&#34;

是的,我知道!但是你肯定会在每一行的末尾看到悬空(fs),对吗?

你不会写这个,对吧?

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

max(3,1);
//=> 3

咄!这与

相同
var max = Math.max;
max(3,1);
//=> 3
  

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

// 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);

&#34;这仍然以同样的方式运作?&#34;

哎呀,确实如此!

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

&#34;但为什么这样更好?&#34;

嗯,这很简单。当然我们有更多代码,但我们现在有 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

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

这就是全部,伙计们!

答案 1 :(得分:1)

其他答案中已经详细介绍了函数组成,主要是https://stackoverflow.com/a/30198265/4099454,所以我的 2 美分直接用于回答您的最新问题:

<块引用>

如果para是一个函数,它会将该函数“组合”到自身中,如果不是则返回最终结果。谢谢!

const chain = (g, f = x => x) => 
  typeof g === 'function'
  ? (y) => chain(y, (x) => g(f(x)))
  : f(g);

// ====

const triple = x => x * 3;
const inc = x => x + 1;
const isZero = x => x === 0;

console.log(
  chain(inc)(triple)(isZero)(-1),
);

答案 2 :(得分:0)

您只需在返回值本身上调用该函数,例如:

plusOne(triple(triple(1))) // 10
isZero(triple(plusOne(-1))) // true

答案 3 :(得分:0)

function triple(x) {
    return x * 3;
}
function plusOne(x) {
    return x + 1;
}
function isZero(x) {
    return x === 0;
}

var combine = function (v) {
    var fn = [];
    function _f(v) {
        if (typeof v === 'function') {
            fn.push(v);
            return _f;
        } else {
            return fn.reduce(function (x, f) { return f(x); }, v);
        }
    }
    return _f(v);
};

var a, b;
console.log(combine(1)); //1
console.log(combine(triple)(triple)(plusOne)(1)); // 10
console.log(combine(plusOne)(triple)(isZero)(-1)); // true
console.log(a = combine(plusOne)); // function ...
console.log(b = a(triple)); // function ...
console.log(b(5)); // 18
console.log(combine(triple)(plusOne)(triple)(plusOne)(triple)(plusOne)(1)); // 40
// @naomik's examples
var f = combine(triple); 
var g = combine(triple)(triple); 
console.log(f(1)); // 3
console.log(g(1)); // 9 (not 6 as you stated)

答案 4 :(得分:0)

也可以通过在 JavaScript 中组合简单函数来构建复杂的功能。从某种意义上说,组合是函数的嵌套,将一个输入的结果作为输入传递给下一个。但与其创建难以理解的嵌套数量,我们将创建一个高阶函数 compose(),它接受我们想要组合的所有函数,并返回一个新函数以在我们的应用中使用。

function triple(x) {
  return x * 3;
}
function plusOne(x) {
  return x + 1;
}
function isZero(x) {
  return x === 0;
}

const compose = (...fns) => x =>
  fns.reduce((acc, cur) => {
    return cur(acc);
  }, x);

const withCompose = compose(triple, triple, isZero);
console.log(withCompose(1));