无法单步执行减少一系列功能的功能

时间:2015-06-14 10:48:46

标签: javascript functional-programming reduce higher-order-functions

在函数数组上使用reduce方法时,我很难跟踪reduce对数组的影响。

comboFunc(num, functionsArr) {
  return functionsArr.reduce(function (last, current) {
    return current(last);
  }, input);
}

所以使用functionsArr = [add, multi]并且函数addmulti

function add(num) {
  return num + 1;
}

function multi(num) {
  return num * 30;
}

函数comboFunc将:

comboFunc(2, functionsArr); //returns 90;

通过将函数应用于数组的每个元素,我理解reduce方法如何处理数组数组,将数组折叠成一个值。这个示例使用comboFunc将函数数组作为参数使用了足够简单的数学运算,我可以理解reduce如何以数学方式生成90的值,但我不理解逐行和从函数级别每次迭代发生的事情。我无法在脑海中清楚地表达出究竟发生了什么 - 如何将2的输入从函数传递到函数以及关闭每个函数的顺序触发等等。

如果reduce方法应用你传入数组的每个元素的函数,并且当数组中的每个元素都是一个函数时...我怎么能写出来自己理解它的外观呢?

传递给reduce方法的函数返回current(last),这是一个返回函数的函数。那么这会使last等于add函数而current等于multi?换句话说,返回multi(add)?如果我的逻辑是正确的,这意味着发生的事情是输入然后被传递到add并计算,然后返回的值被传递到multi并计算最终返回90?

3 个答案:

答案 0 :(得分:2)

看起来您正在尝试创建一个composition函数,该函数将一系列函数应用于输入, num

让我们首先介绍一些语法错误。首先,您错过了function关键字

// your code
comboFunc(num, functionsArr) {

// should be
function comboFunc(num, functionsArr) {

接下来,您已将参数命名为num,但之后在函数体中将其引用为input

// your code
}, input);

// should be
}, num);

因此,在您解决此问题后,您的功能会起作用,但如果我们以稍微不同的方式处理问题该怎么办?

  

免责声明:此答案并未指导您完成所提供的功能。相反,它通过证明comboFunc以过于复杂的方式编写而同情你的困难。反过来,我给你一个函数的替代定义,它的操作相同,但是 lot 更容易阅读/理解。

首先,我认为我们应该回到函数组合的基础知识。

我喜欢将功能构图描绘成一张小地图。

  f(x)=y      g(y)=z
+--------> y -------+
|                   |
|                   |
|                   v
x ----------------> z
          ?

在上面的示例中,我们可以使用您的add函数轻松替换 f ,使用multi函数轻松替换 g 。现在定义也应该是相当明显的。

  add(2)      multi(3)
+--------> 3 -------+
|                   |
|                   |
|                   v
2 ----------------> 90
  h(x) = g(f(x))  = z
  h(x) = (g∘f)(x) = z
  h(2)            = 90

(g∘f)可以说是“ g f ”,它只是意味着应用 x 首先到 f ,然后将该结果应用于 g

注意 y 甚至不存在。我们根据 x 定义了 h ,它为我们提供了直接“地图”到 z ,跳过 y < / em>完全。

将两个函数组合在一起是一个非常基本的操作,我们可以定义一个非常容易实现的函数

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

var h = comp(multi,add);

   h(2);                   // 90
// h(2) = multi(add(2))    =  90

“是的,但我正在尝试使用N个功能。不仅仅是2个。”

好的,comp适用于两个功能,但我已经简化了问题。如果我们想要组合3,4或甚至更多功能,你可能想知道这是如何工作的。

让我们再看一遍

a(w)              = x
b(x)              = y
c(y)              = z
d(w) = c(b(a(w))) = z
d(w) = (c∘b∘a)(w) = z

仔细查看。我们知道这是我们的comp函数,我们可以看到它出现在每个函数之间, a b c

在我们走得更远之前,假设我们有一组数字

var xs = [1,2,3];

如果我们想要对数字求和,我们需要调用

var sum = 1 + 2 + 3

查看每个号码之间+的显示方式?通过我们的一系列功能,它没有什么不同!

var fs = [c,b,a];
var d  = (c ∘ b ∘ a)

我们只需要制作有效的JavaScript。它也很容易,因为我们已经将定义为comp

在列表中的术语之间调用函数称为线性fold,JavaScript具有执行此操作的函数reduce

好吧,让我们用吧!我们将使用comp函数

折叠(“缩小”)我们的函数列表
function compN(fs) {
  return fs.reduce(comp);
}

var h = compN([multi, add]);

   h(2);                   // 90
// h(2) = multi(add(2))    =  90
// h(2) = (multi ∘ add)(2) =  90

好的,所以它适用于我们原来的两个功能,但让我们确保它适用于更长的列表

var i = compN([multi, add, add, add]);

   i(2);                               // 150
// i(2) = multi(add(add(add(2)))       =  150
// i(2) = (multi ∘ add ∘ add ∘ add)(2) =  150
  

好的,让我们回顾一下

function add(num) {
  return num + 1;
}

function multi(num) {
  return num * 30;
}

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

function compN(fs) {
  return fs.reduce(comp);
}

compN([multi, add])(2); // 90

“但为什么这样更好?”

正如我们在y中删除h(x) = (g∘f)(x) = z一样,请注意我们的compN函数与numlast或{{ 1}}。我们已经放弃了3个变量供我们的大脑思考,而且我们的功能正在做得更清楚。

current使用 f g 并将它们组合在一起制作comp

g ∘ f在功能列表中的每个字词之间调用compN

对我而言,这是非常直接且易于理解的。只需查看每个compcomp的函数体,我就可以立即识别出意图。

  

将其与

进行比较
compN

难怪你难以跟上它。 comboFunc(num, functionsArr) { return functionsArr.reduce(function (last, current) { return current(last); }, input); } 从一开始就过多地关注自己,因为它试图成为两个操作而不是一个操作。

所以,我知道我并没有引导你完成你的原始功能,但我希望在阅读完这个答案之后,你会对功能构成有更深刻的理解,并建立自己独立的功能。

修改

根据下面的一些讨论,我们的comboFunc函数处理空数组compN是很有价值的。

[]

Yuck!此处的预期行为是var f = compN([]); // Uncaught TypeError: Reduce of empty array with no initial value 创建一个返回未修改输入的函数。

compN

使用this作为reduce的起点将保证即使给出空数组,我们的function id(x) { return x; } 函数也能正常工作

compN

检查出来

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

现在compN([])(2); // 2 compN([multi, add])(2); // 90 适用于0个或更多函数的数组。

答案 1 :(得分:1)

你调用它的方式(有两个参数),Array#reduce为数组中的每个条目调用一次回调。

在第一次调用时,last参数是您在结尾处给出的值(在您的情况下为input),current参数是数组中的第一个条目。你的回调是调用那个传入last的条目(你的第一个函数),所以用2调用第一个函数。由于这是add,因此调用它的结果为3,然后您将从回调中返回。

在第二次调用时,last参数是您在上一次调用时返回的参数;所以它是3current参数是数组中的下一个函数。在您的情况下,那是multi,因此您可以使用3来调用它。 multi会返回您返回的90

reduce的结果是回调返回的最后一个值。所以,90,在您的示例中。

您可以将reduce的这种形式视为这样(字面意思是它的作用,reduce更复杂;这是一个简化版本):< / p>

// Again, this is a *simplified* example
function pseudoReduce(array, callback, initialValue) {
    var index, last;

    last = initialValue;
    for (index = 0; index < array.length; ++index) {
        last = callback(last, array[index], index, array);
    }
    return last;
}

Array#reduce如果你没有给第二个参数,那么在第一次传球时会有不同的略微 ;这不会反映在上面。)

答案 2 :(得分:1)

了解所发生情况的最简单方法是在代码中添加console.log(),以便逐步了解正在发生的事情:

function comboFunc(num, functionsArr) {
  return functionsArr.reduce(function (last, current) {
    console.log(current + " " + last + " ==> " + current(last));
    return current(last);
  }, num);
}

function add(num) {
  return num + 1;
}

function multi(num) {
  return num * 30;
}

var functionsArr = [add, multi];
console.log( comboFunc(2, functionsArr) );

输出:

"function add(num) {
  return num + 1;
} 2 ==> 3"
"function multi(num) {
  return num * 30;
} 3 ==> 90"
90

这基本上是

multi(add(2)); // (2+1)*30 = 90