JavaScript转换

This is a continuation of the previous article I wrote titled, Fusion in JavaScript. Fusion is a technique of combining pure functions — taking advantage of composition and removing the intermediary copies of data on each layer. If you haven’t read about it, please do so! You can find it here.

这是我撰写的前一篇文章“ JavaScript中的Fusion ”的延续。 融合是一种结合纯功能的技术-利用组合的优势,并删除每一层数据的中间副本。 如果您尚未阅读,请阅读! 你可以在这里找到它。

Trans转导导论 (📖 Introduction to Transduction)

In applying the Fusion technique, you can only use it if all the functions have the same shape of arguments and the same shape of return. Here’s our example back there

在应用Fusion技术时,仅当所有函数的参数 形状和return形状相同时 ,才可以使用它。 这是我们后面的例子

// fusion approach


const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const add = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const res = data
  .map(
    pipe(add, multiplyBy2)
  )


// [ 4, 6, 8, 10, 12 ]

You can see that our mapper functions have the same shape — both accept a number and they both return the same shape. A sum and a product.

您可以看到我们的映射器函数具有相同的形状-都接受数字,并且都返回相同的形状。 一笔和与一个乘积。

That’s the Fusion technique. For us to “fuse” the functions or compose them, we have to follow a rule. A rule that in order for us to fuse or compose our functions they should have the same function shape. On our example, the add and multiplyBy2 both have the same shape and that’s why we were able to take advantage of composition.

那就是融合技术。 为了使我们“融合”功能或组成功能,我们必须遵循规则。 为了使我们融合或组成我们的功能,它们应该具有相同的功能形状。 在我们的示例中, addmultiplyBy2都具有相同的形状,这就是为什么我们能够利用组合的原因。

But, what if there’s an additional requirement? Let’s say we need to filter our result by only getting the numbers below 10 and get the total of all the numbers?

但是,如果还有其他要求怎么办? 假设我们需要通过仅使数字低于10并获得所有数字的总和来过滤结果。

Okay, I hear you. We will add Array.prototype.filter() to remove other items since we only need the items that are below 10 and an Array.prototype.reduce() to get the total of all the numbers. That’s actually correct!

好的,我听到了。 我们将添加Array.prototype.filter()删除其他项,因为我们只需要10以下的项,并需要Array.prototype.reduce()来获取所有数字的总数。 这是正确的!

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const res = data
  .map(
    pipe(addBy1, multiplyBy2)
  )
  .filter(getItemsBelow10)
  .reduce(sum, 0)


// 18

But, this approach suffers also from the fact that on each chain layer, it will create a new copy of the data and iterate on each item again to apply the function.

但是,该方法还受到以下事实的困扰:在每个链层上,它将创建数据的新副本,并在每个项目上再次进行迭代以应用该功能。

Maybe you are now starting to wonder, is it possible to combine Array.prototype.map(), Array.prototype.filter() and Array.prototype.reduce() into a single call to avoid creating intermediary copies of data on each layer?

也许您现在开始怀疑,是否可以将Array.prototype.map()Array.prototype.filter()Array.prototype.reduce()合并到一个调用中,以避免在每一层上创建数据的中间副本?

The answer is YES and that’s where Transduction will come! That is our goal, to put thoseArray.prototype.map(), Array.prototype.filter() and Array.prototype.reduce() into a single call.

答案是肯定的,那就是转导的到来! 这是我们的目标,是将Array.prototype.map()Array.prototype.filter()Array.prototype.reduce()放入一个调用中。

🧬全部减少 (🧬 Reduce Them All)

Before we try to implement Transduction technique, it’s important to realize how this specific method that I’m going to tell you is powerful.

在我们尝试实现转导技术之前,重要的是要认识到我要告诉您的这种特定方法是多么强大。

The Array.prototype.reduce() is a powerful function because it allows you to implement anything you would like. You can implement the logic of Array.prototype.filter() inside it, also the logic of Array.prototype.map() and so on!

Array.prototype.reduce()是强大的函数,因为它允许您实现所需的任何东西。 您可以实现的逻辑Array.prototype.filter()里面,还有的逻辑Array.prototype.map()等等!

Let’s see how we can implement our map and filter inside the reduce as we move forward.

让我们看看如何在前进时在reduce中实现地图和过滤器。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
// const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => (accumulator, currentValue) => {
  accumulator.push(mapperFn(currentValue));
  return accumulator;
};


const filterReduce = predicateFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    accumulator.push(currentValue);
  }
  return accumulator;
};


const res = data
  .reduce(mapReduce(addBy1), [])
  .reduce(mapReduce(multiplyBy2), [])
  .reduce(filterReduce(getItemsBelow10), [])
  .reduce(sum, 0)


// 18

I have removed the implementation of pipe for now to avoid extra confusion with the new functions created. There’s also some ground-breaking understanding of the flow of data when using thepipe or compose utilities which I’ll be discussing as we go on.

我现在已经删除了pipe的实现,以避免与创建的新函数产生额外的混淆。 使用pipecompose实用程序时,我对数据流也有一些突破性的理解,我们将在后面继续讨论。

We’ve created mapReduce and filterReduce as curried functions because in functional programming it is inconvenient to have more than one argument because of composition. These helper functions allow us to use our functions inside Array.prototype.reduce() and make it “compatible” with the Array.prototype.reduce() signature. If you will observe the two functions, you can see that on the 2nd call of the function, it is expecting two inputs (accumulator, currentValue). That function signature is the signature from the Array.prototype.reduce() . We’ve curried the two functions because that allows us to partially create the function or in other words, lazy evaluation.

我们已将mapReducefilterReduce创建为咖喱函数,因为在函数式编程中,由于组成原因,不方便使用多个参数。 这些帮助器函数使我们能够在Array.prototype.reduce()使用我们的函数,并使它与Array.prototype.reduce()签名“兼容”。 如果您将观察这两个函数,则可以看到在该函数的第二次调用中,它期望有两个输入(累加器,currentValue)。 该函数签名是Array.prototype.reduce()的签名。 我们对这两个函数进行了咖喱处理,因为这使我们可以部分创建该函数,或者换句话说,可以进行惰性求值。

This is how it looks like without those two functions utilities in raw form.

没有原始功能的这两个函数实用程序就是这样。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
// const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => (accumulator, currentValue) => {
  accumulator.push(mapperFn(currentValue));
  return accumulator;
};


const filterReduce = predicateFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    accumulator.push(currentValue);
  }
  return accumulator;
};


const res = data
  .reduce((accumulator, currentValue) => {
    accumulator.push(addBy1(currentValue));
    return accumulator;
  }, [])
  .reduce((accumulator, currentValue) => {
    accumulator.push(multiplyBy2(currentValue));
    return accumulator;
  }, [])
  .reduce((accumulator, currentValue) => {
    if (getItemsBelow10(currentValue)) {
      accumulator.push(currentValue);
    }
    return accumulator;
  }, [])
  .reduce(sum, 0)


// 18

If we can do it in this raw form, why did we implement some curried functions?

如果我们能够以这种原始形式进行操作,为什么我们要实现一些咖喱函数?

Look at those reductions (the functions inside the Array.prototype.reduce()) and you will see something in common.

查看这些简化( Array.prototype.reduce()内部的函数),您将看到一些共同点。

Have you spotted it?

你发现了吗?

Yes, those accumulator.push and returning the accumulator declarations are called combiner functions. A combiner function is simply a function that combines the result. A combiner function is not limited to combining items to the list. In fact, it can combine anything! Here on our example, it is doing accumulator.push which sounded like a “concat” combiner. Let’s create a combiner function and name it combinerConcat .

是的,那些accumulator.push和返回accumulator声明的函数称为合并器函数 。 组合器功能只是将结果组合在一起的功能。 组合器功能不限于将项目组合到列表。 实际上,它可以结合任何东西! 在我们的示例中,它正在做accumulator.push ,听起来像是“ concat”组合器。 让我们创建一个组合器函数,并将其命名为combinerConcat

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
// const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => (accumulator, currentValue) => {
  accumulator.push(mapperFn(currentValue));
  return accumulator;
};


const filterReduce = predicateFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    accumulator.push(currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};


const res = data
  .reduce((accumulator, currentValue) => {
    return combinerConcat(accumulator, addBy1(currentValue));
  }, [])
  .reduce((accumulator, currentValue) => {
    return combinerConcat(accumulator, multiplyBy2(currentValue));
  }, [])
  .reduce((accumulator, currentValue) => {
    if (getItemsBelow10(currentValue)) {
      return combinerConcat(accumulator, currentValue);
    }
    return accumulator;
  }, [])
  .reduce(sum, 0)


// 18

Okay, that looks good… We’ve extracted our combiner function and that gives us a somehow generic combiner function on our Array.prototype.reduce() calls.

好的,这看起来不错……我们提取了合并器函数,从而在Array.prototype.reduce()调用上为我们提供了某种通用的Array.prototype.reduce()器函数。

But, there’s a problem with this raw version and why it is important to switch to the curried functions. With this raw version, we will not be able to take advantage of composition and won’t allow us to reduce our calls into a single call operation.

但是,这个原始版本存在一个问题,以及为什么切换到咖喱函数很重要。 使用此原始版本,我们将无法利用合成,也不允许我们将调用减少为单个调用操作。

Let’s tidy it up as this will also prepare us for the succeeding steps.

让我们整理一下,因为这也将使我们为后续步骤做好准备。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
// const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};


const res = data
  .reduce(mapReduce(addBy1)(combinerConcat), [])
  .reduce(mapReduce(multiplyBy2)(combinerConcat), [])
  .reduce(filterReduce(getItemsBelow10)(combinerConcat), [])
  .reduce(sum, 0)


// 18

We haven’t reduced our calls into a single call. But, hang on there! We’re almost there! It will make sense later on why we need to curry it!

我们还没有将通话减少到一个通话中。 但是,等一下! 我们快到了! 稍后我们为什么要咖喱这才有意义!

I want you to be aware of the result. It is 18 and that what we should be expecting throughout the end result.

我希望您知道结果。 现在是18 ,在整个最终结果中我们应该期待的是。

With超越与转导 (📈 Going Above With Transduction)

Transduction is a process of making Array.prototype.map(), Array.prototype.filter() and Array.prototype.reduce() to be compatible with each other regardless if they have different function shape.

转导是使Array.prototype.map()Array.prototype.filter()Array.prototype.reduce()相互兼容的过程,无论它们具有不同的函数形状。

Kyle Simpson on the frontend masters course said that Transduction is a way to use a mathematical process to reshape map and filter into reducers so that map, filter, and reduce can all be used in conjunction.

前端大师课程的Kyle Simpson说,转导是一种使用数学过程将贴图和过滤器整形为化简器的方法,因此贴图,过滤器和化简可以一起使用。

Transduction uses transducers to compose multiple reducers in order for those reducers to be composable with each other.

转导使用换能器组成多个异径管,以便这些异径管可相互组合。

A transducer is a higher-order reducer or a composed reducer. A function that is composed of reducers, accepts a reducer, and returns a reducer.

换能器是高阶减速器或组合减速器。 由reduce组成的函数,接受reduce,然后返回reduce。

Compared with normal reducers, they are not composable because their signature is different. They accept two inputs (accumulator, currentValue) and returns a single value. With transducer, it accepts a reducer and returns a reducer. And that makes the transducer valid for composition.

与普通的异径管相比,它们的成分不同,因此无法合成。 它们接受两个输入(累加器,currentValue)并返回单个值。 使用换能器时,它接受减速器并返回减速器。 这使得换能器有效地用于合成。

On our last example, we were able to convert those Array.prototype.map() and Array.prototype.filter() in a way of Array.prototype.reduce(). That’s actually great progress because we are now able to reshape it into a common signature. Which then, if functions have the same signature, it means we can take advantage of…? Guess what! Yes, Composition!

在最后一个示例中,我们能够以Array.prototype.filter()的方式转换Array.prototype.map()Array.prototype.reduce() 。 这实际上是一个很大的进步,因为我们现在能够将其重塑为一个通用的签名。 那么,如果函数具有相同的签名,那意味着我们可以利用…? 你猜怎么了! 是的,构图!

// From here
const res = data
  .map(
    pipe(addBy1, multiplyBy2)
  )
  .filter(getItemsBelow10)
  .reduce(sum, 0)
  
  
  
// To here
const res = data
  .reduce(mapReduce(addBy1)(combinerConcat), [])
  .reduce(mapReduce(multiplyBy2)(combinerConcat), [])
  .reduce(filterReduce(getItemsBelow10)(combinerConcat), [])
  .reduce(sum, 0)

We haven’t reduced it into a single call and that’s what we are going to do now! Let’s try that one.

我们还没有将其简化为一个通话,而这就是我们现在要做的! 让我们尝试一下。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};


const transducer = pipe(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10)
);


const res = data
  .reduce(transducer(combinerConcat), [])
  .reduce(sum, 0)


// 35

We’ve now removed the comments on our pipe and use it to create a transducer in line 37. We now know that a transducer is a higher-order reducer or a composed reducer.

现在,我们删除了pipe上的注释,并使用它在第37行中创建了一个换能器。我们现在知道, 换能器是高阶减速器或组合减速器。

We have two new things here. The first one is transducer which we will tackle shortly and the last one is the result. It is now 35 and not 18 . Remember when I told you to aware of that? We will address it after our transducer . Hang-on tight!

我们在这里有两个新事物。 第一个是transducer ,我们将很快解决,最后一个是结果。 现在是35而不是18 。 还记得我告诉你的那件事吗? 我们将在transducer 。 挂紧!

You might wonder about our transducer, why did we not have them combinerConcat on it?

您可能想知道我们的换能器,为什么我们没有在其上使用combinerConcat

const transducer = pipe(
  mapReduce(addBy1)(combinerConcat),
  mapReduce(multiplyBy2)(combinerConcat),
  filterReduce(getItemsBelow10)(combinerConcat)
);


const res = data
  .reduce(transducer, [])
  .reduce(sum, 0)

The reason is that will break the signature of our reducers. Let me show you why it will break the signature of our reducers.

原因是将破坏我们的减速器的签名。 让我告诉你为什么它会破坏我们的减速器的特征。

const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};


const transducer = pipe(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10)
);


const mapTransducer = mapReduce(addBy1)
// mapTransducer:: combinerFn -> (accumulator, currentValue) -> result


const filterTransducer = filterReduce(getItemsBelow10)
// filterTransducer:: combinerFn -> (accumulator, currentValue) -> result




const mapTransducerWithCombiner = mapReduce(addBy1)(combinerConcat)
// mapTransducerWithCombiner:: (accumulator, currentValue) -> result


const filterTransducerWithCombiner = filterReduce(getItemsBelow10)(combinerConcat)
// filterTransducerWithCombiner:: (accumulator, currentValue) -> result


// Execution of `mapTransducerWithCombiner` and `filterTransducerWithCombiner` in the `pipe`


const transducerWithCombiner = pipe(
  mapReduce(addBy1)(combinerConcat), // returns a result
  filterReduce(getItemsBelow10)(combinerConcat), // expects a (accumulator, currentValue) -> result 
);


// BREAK! Incorrect function signature

We can see that the transducer with a combiner will make the signature kinda like the normal reducers. It accepts two inputs (accumulator, currentValue). We also understand that normal reducers aren’t composable because of their signature are different compared with transducers.

我们可以看到带有组合器的换能器会像普通的减速器那样使签名变得有点像。 它接受两个输入(累加器,currentValue)。 我们还了解到,正常的异径管是不可组合的,因为其特征与换能器相比有所不同。

Here’s our statement from the start of this topic:

这是从本主题开始的声明:

Compared with normal reducers, they are not composable because their signature is different. They accept two inputs (accumulator, currentValue) and returns a single value. With transducer, it accepts a reducer and returns a reducer. And that makes the transducer valid for composition.

与普通的异径管相比,它们的成分不同,因此无法合成。 它们接受两个输入(累加器,currentValue)并返回单个值。 使用换能器时,它接受减速器并返回减速器。 这使得换能器有效地用于合成。

In order for our transducer to be valid for composition, the function shapes should be the same for all the functions.

为了使我们的换能器有效地进行合成,所有功能的功能形状均应相同。

That is why our transducer doesn’t have a combinerFn . I know that is hard to digest. Take your time. I still have a hard time wrapping my brain about it.

这就是为什么我们的换能器没有combinerFn 。 我知道这很难消化。 慢慢来。 我仍然很难解决这个问题。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `pipe` is the opposite of `compose`
 */
const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
}


const transducer = pipe(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10)
);


const res = data
  .reduce(transducer(combinerConcat), [])
  .reduce(sum, 0)


// 35

Let’s now get back with our result.

现在让我们返回结果。

Why is it 35 and not 18? Our pipe’s flow looks the same with our initial implementation.

为什么是35而不是18 ? 我们的管道流程与我们最初的实施情况相同。

// Initial implementation


const res = data
  .reduce(mapReduce(addBy1)(combinerConcat), [])
  .reduce(mapReduce(multiplyBy2)(combinerConcat), [])
  .reduce(filterReduce(getItemsBelow10)(combinerConcat), [])
  .reduce(sum, 0)
  // 18


// New implementation


const transducer = pipe(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10),
);
  
const res1 = data
  .reduce(transducer(combinerConcat), [])
  .reduce(sum, 0)


  // 35

Do you remember why I commented out our pipe function awhile ago? The reason is that thepipe and compose behaves differently when applying in the transduction.

您还记得为什么我前一段时间注释掉了pipe函数吗? 原因是pipecompose在转导中的应用表现不同

When we say it behaves differently, what do we mean by that? We understand that the execution ofpipe runs from left-to-right and compose runs from right-to-left.

当我们说它的行为不同时,这是什么意思? 我们知道pipe的执行从左到右,而compose执行从右到左。

/**
 * 
 * These utility functions are often available on various 
 * functional libraries (i.e ramdajs)
 * `compose` runs from right-to-left
 * `pipe` runs from left-to-right
 */
const compose = (...fns) => (initialValue) => fns.reduceRight((d, f) => f(d), initialValue);
const pipe = (...fns) => (initialValue) => fns.reduce((d, f) => f(d), initialValue);


const data = 5;


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const composeResult = compose(multiplyBy2, addBy1)(data);


// 12


const pipeResult = pipe(addBy1, multiplyBy2)(data);


// 12

We can see that compose executes those functions starting from the end (right) until to the start (left). It is running from right to left indeed.

我们可以看到compose从结束(右)到开始(左)执行这些功能。 它确实从右到左运行。

We can also see that pipe executes those functions starting from the start (left) until to the end (right). It is running from left to right indeed.

我们还可以看到pipe从开始(左)到结束(右)执行这些功能。 它确实从左到右运行。

Those rules are reversed when it is applied in transduction. I didn’t know this at first. I spent almost 2hrs figuring out why this is happening at midnight. I did a lot of research but something is not clicking. I can’t seem to understand what I am reading from different articles.

这些规则在转导中应用时会被颠倒。 我一开始不知道这一点。 我花了将近2个小时才弄清楚为什么会在午夜发生。 我做了很多研究,但是没有点击。 我似乎无法理解我从其他文章中读到的内容。

My last option is to contact Kyle Simpson on Twitter to shed some light on me.

我的最后一个选择是在Twitter上与Kyle Simpson联系,以向我阐明一些信息。

Shooting for the moon! After waking up, he indeed gave an answer and it starts clicking and making sense! So grateful!

为月亮射击! 醒来后,他的确回答了,它开始点击并有意义! 太感谢了!

This is what he said to my problem.

这就是他对我的问题所说的。

Image for post
https://gist.github.com/jmaicaaan/7f217cf3f7d638596d7c503a4083f491 https://gist.github.com/jmaicaaan/7f217cf3f7d638596d7c503a4083f491

That is confusing at first but I re-read multiple times to start clicking. In addition to that answer, the reason why we are getting a different result is that we think that the “data” that is flowing through the pipe is the actual value — numbers from our list. But, that is incorrect.

起初这很令人困惑,但我多次阅读后开始点击。 除了该答案之外,我们得到不同结果的原因是,我们认为流经pipe的“数据”是实际值,即清单中的数字 。 但是,那是不正确的

A mental shift is needed.

需要精神上的转变。

The “data” that is flowing through the pipe is the “reducer” function and not the actual number from our array. It is actually our combinerFn.

流经pipe的“数据”是“ reducer”功能,而不是数组中的实际数字。 实际上是我们的combinerFn

With that one, let’s replace our pipe with compose as that would feel “natural” in the flow.

有了它,让我们用compose替换pipe ,因为它在流程中会感觉“自然”。

// Let's replace our `pipe` with `compose`.


// const pipe = (...fns) => initialValue => fns.reduce((result, fn) => fn(result), initialValue);


const compose = (...fns) => initialValue => fns.reduceRight((result, fn) => fn(result), initialValue);

After changing that one, let’s update our transducer as well and see the result.

更改该传感器后,让我们也更新传感器并查看结果。

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `compose` is the opposite of `pipe`
 */
const compose = (...fns) => initialValue => fns.reduceRight((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const sum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};


const transducer = compose(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10)
);


const res = data
  .reduce(transducer(combinerConcat), [])
  .reduce(sum, 0)


// 18

Hooray! Our result is now correct! Pat your back for sticking through it.

万岁! 我们的结果现在是正确的! 拍打您的背部,以坚持下去。

We’re almost there with our final step to complete this journey! We haven’t reduced our calls into a single call. We’ve now achieved to combine Array.prototype.map() and Array.prototype.filter() into a single call but there’s still one more step that we need to do.

我们即将完成最后的旅程! 我们还没有将通话减少到一个通话中。 现在,我们已经实现了将Array.prototype.map()Array.prototype.filter()到一个调用中,但是还需要执行更多步骤。

Take a look closely on the combinerConcat and sum function.

仔细看看combinerConcatsum函数。

const sum = (accumulator, currentValue) => accumulator += currentValue;


const combinerConcat = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  return accumulator;
};

What do you notice? They both have the same signature. They accept the same input signature and return the same value signature.

你注意到了什么? 它们都具有相同的签名。 它们接受相同的输入签名并返回相同的值签名。

The sum function is also a combiner function! And knowing that it is a combiner function as well. We can now remove our combinerConcat and put the sum combiner function in that!

sum函数也是组合器函数! 并且知道它也是一个组合器功能。 现在,我们可以删除我们的combinerConcat并将sum组合器函数放到其中!

const data = [
  1,
  2,
  3,
  4,
  5,
];


/**
 * 
 * This utility function is often available on various 
 * functional libraries (i.e ramdajs)
 * `compose` is the opposite of `pipe`
 */
const compose = (...fns) => initialValue => fns.reduceRight((result, fn) => fn(result), initialValue);


const addBy1 = (x) => x + 1;
const multiplyBy2 = (x) => x * 2;


const getItemsBelow10 = (x) => x < 10;
const combinerSum = (accumulator, currentValue) => accumulator += currentValue;


const mapReduce = mapperFn => combinerFn => (accumulator, currentValue) => {
  return combinerFn(accumulator, mapperFn(currentValue));
};


const filterReduce = predicateFn => combinerFn => (accumulator, currentValue) => {
  if (predicateFn(currentValue)) {
    return combinerFn(accumulator, currentValue);
  }
  return accumulator;
};


const transducer = compose(
  mapReduce(addBy1),
  mapReduce(multiplyBy2),
  filterReduce(getItemsBelow10)
);


const res = data
  .reduce(transducer(combinerSum), 0)


// 18

We’ve replaced the initial value from [] to 0 as well because our combiner function — combinerSum is dealing with summing numbers and not working with the collection/list.

我们也将初始值从[]替换为0 ,因为我们的合并器函数— combinerSum处理的是对数字求和,而不处理集合/列表。

We’ve now applied the Transduction technique and that should greatly help us in terms of performance and also provides readability and easier to reason out on our code.

现在,我们已经应用了Transduction技术,这将在性能方面极大地帮助我们,并且还提供了可读性并且更容易推断出我们的代码。

Together整合在一起 (🤖 Bringing It All Together)

We’ve converted those Array.prototype.map() , Array.prototype.filter() , and Array.prototype.reduce() into a single call by making them compatible with each other. Making their function signatures be the same in order for us to take advantage of composition. That is the Transduction — the process of converting those functions into a compatible shape through transducers.

我们通过使它们彼此兼容,将Array.prototype.map()Array.prototype.filter()Array.prototype.reduce()转换为单个调用。 使它们的功能签名相同,以便我们利用组合。 这就是转导-通过换能器将这些功能转换为兼容形状的过程。

There are libraries such a Ramda.js and transducer-js that will you out implementing this and you don’t have to go through implementing this on your own. The goal of this article is to give us knowledge and understanding of how these things work, what problems it is solving, and how we can apply it to our code.

有诸如Ramda.jsTranslator -js之类的库,您可以自行实现,而不必亲自实现。 本文的目的是使我们了解并理解这些事物的工作方式,正在解决的问题以及如何将其应用于代码。

If you are interested in checking it out more, here are some references:

如果您有兴趣进一步检查,请参考以下内容:

Thank you for reading. I hope this will help you on your journey! ❤️

感谢您的阅读。 希望这对您的旅途有所帮助! ❤️

翻译自: https://medium.com/weekly-webtips/transduction-in-javascript-fbe482cdac4d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值