JavaScript函数式编程介绍:融合与转换

news/2024/7/5 20:58:31

介绍 (Introduction)

Fusion and transduction may be the most practical tools I’ve picked up in my time studying Functional Programming. They’re not tools I use every day, nor are they strictly necessary, but, they completely changed the way I think about programming, modularity, and abstraction in software engineering, permanently, and for the better.

融合和转导可能是我在学习函数式编程时获得的最实用的工具。 它们不是我每天使用的工具,也不是绝对必要的工具,但是,它们彻底改变了我对软件工程中的编程,模块化和抽象的思考的方式,这是永久性的,并且是更好的。

And to be clear, that’s really the point of this article: Not to evangelize FP, offer a silver bullet, or illuminate some magic secret sauce that’s “better” than what you do now. Rather, the point is to shed light on different ways of thinking about programming, and broaden your sense of possible solutions to everyday problems.

需要明确的是,这实际上是本文的重点:不要宣传FP,提供银弹或照亮一些比您现在做的更好的神奇秘密调味料。 相反,关键是要阐明编程的不同思考方式,并扩大您对日常问题的可能解决方案的意识。

These aren’t easy techniques to use fluently, and it’ll probably take some time, tinkering, and deliberate practice to fully comprehend what’s going on here. For most, it’s an entirely new level of abstraction.

要流利使用这些技术并非易事,可能需要一些时间,细心研究和认真练习才能完全理解这里发生的事情。 对于大多数人来说,这是一个全新的抽象水平。

But, if you put in the time, you might just come out with the sharpest sense of abstractions around functions that you’ve ever had.

但是,如果投入时间,您可能只会对所拥有的功能产生最清晰的抽象感。

一个简单的例子 (A Quick Example)

Recall the definition of pure functions as functions which have no side effects, and always returns the same value for any given input.

回顾纯函数的定义是没有副作用的函数,并且对于任何给定的输入始终返回相同的值。

Since pure functions always return the same value for a given input, we can safely pass their return values directly to other functions.

由于纯函数总是为给定的输入返回相同的值,因此我们可以安全地将其返回值直接传递给其他函数。

This enables such niceties as:

这样可以实现以下优点:

// niceties
colorBackground(wrapWith(makeHeading(createTitle(movie))), 'div')), 'papayawhip')

Here, we use makeHeading to create a string heading out of movie; use this string to create a new heading (makeHeading delegates to document.createElement); wrap this heading in a div; and finally, call colorBackground, which updates the element’s styling to set a background of papayawhip…Which is my favorite flavor of CSS.

在这里,我们使用makeHeading创建一个字符串的标题出来movie ; 使用此字符串创建一个新的标题( makeHeading委托给document.createElement ); 把这个标题div ; 最后,调用colorBackground ,它会更新元素的样式以设置papayawhip的背景…这是我最喜欢CSS风格。

Let’s be explicit about the composition at work in this snippet. At each step of the pipeline, a function accepts an input, and returns an output, which the input determines completely. More formally: At each step, we add another referentially transparent function to the pipeline. Yet more formally: papayaWhipHeading is a composition of referentially transparent functions.

让我们明确说明一下此片段中的工作内容。 在流水线的每个步骤中,一个函数接受一个输入,然后返回一个输出,由输入完全确定。 更正式地讲:在每个步骤中,我们都会向管道添加另一个参照透明函数。 更正式地说: papayaWhipHeading是参照透明函数的组合。

It’s worth pointing out that a functional eye might spot the below possibility, as well. But you’re not here for illustrative-yet-contrived examples. You’re here to learn about fusion.

值得指出的是,实用的眼睛也可能发现以下可能性。 但是,您还没有这里的说明性示例。 您是在这里学习融合的

Let’s dash through the rest of those prerequisites, and look at chaining Array methods.

让我们来看看其余的先决条件,然后看看如何链接Array方法。

链接映射和过滤器表达式 (Chaining Map and Filter Expressions)

One of the nicer features of map is that it automatically returns an array with its results.

map的更好功能之一是它会自动返回带有结果的数组。

const capitalized = ["where's", 'waldo'].map(function(word) {
  return word.toUpperCase();
});

console.log(capitalized); // ['WHERE'S', 'WALDO']

Of course, there’s nothing particularly special about capitalized. It has all the same methods any other array does.

当然, capitalized没有什么特别的。 它具有与其他数组相同的所有方法。

Since map and filter return arrays, we can chain calls to either method directly to their return values.

由于mapfilter返回数组,我们可以将对任一方法的调用直接链接到其返回值。

const screwVowels = function(word) {
  return word.replace(/[aeiuo]/gi, '');
};

// Calling map on the result of calling map

const capitalizedTermsWithoutVowels = ["where's", 'waldo']
  .map(String.prototype.toUpperCase)
  .map(screwVowels);

This isn’t a particularly dramatic result: Chained array methods like this are common in JS-land. But, it merits attention for its leading to code like the following:

这不是一个特别引人注目的结果:像这样的链式数组方法在JS-land中很常见。 但是,由于其导致如下所示的代码而值得关注:

// Retrieve a series of 'posts' from JSON Placeholder (for fake demonstration data)
// GET data
fetch('https://jsonplaceholder.typicode.com/posts')
  // Extract POST data from response
  .then(data => data.json())
  // This callback contains the code you should focus on--the above is boilerplate
  .then(data => {
    // filter for posts by user with userId == 1
    const sluglines = data
      .filter(post => post.userId == 1)
      // Extract only post and body properties
      .map(post => {
        const extracted = {
          body: post.body,
          title: post.title
        };

        return extracted;
      })
      // Truncate "body" to first 17 characters, and add 3-character ellipsis
      .map(extracted => {
        extracted.body = extracted.body.substring(0, 17) + '...';
        return extracted;
      })
      // Capitalize title
      .map(extracted => {
        extracted.title = extracted.title.toUpperCase();
        return extracted;
      })
      // Create sluglines
      .map(extracted => {
        return `${extracted.title}\n${extracted.body}`;
      });
  });

This is maybe a few more map calls than is common, sure…But, consider map alongside filter, and this style becomes a lot more believable.

这也许是多了一些map调用比是共同的,肯定...但是,考虑map旁边filter ,而这种风格成为了很多更可信。

Using “single-purpose” callbacks in sequential calls to map and filter lets us write simpler code, at the cost of overhead due to function invocation and the requirement for “single-purpose” callbacks.

在顺序调用中使用“单一用途”回调来mapfilter器使我们可以编写更简单的代码,但由于函数调用和“单一用途”回调的要求而导致开销。

We also enjoy the benefits of immutability, as map and filter don’t modify the array you call them on. Rather, they create new arrays each time.

我们还享受不变性的好处,因为mapfilter不会修改您调用它们的数组。 相反,它们每次都会创建新的数组。

This lets us avoid confusion due to subtle side effects, and preserves the integrity of our initial data source, allowing us to pass it to multiple processing pipelines without issue.

这样可以避免由于细微的副作用而造成的混乱,并保留了初始数据源的完整性,从而使我们可以毫无问题地将其传递到多个处理管道。

中间阵列 (Intermediate Arrays)

On the other hand, allocating a whole new array on every invocation of map or filter seems a little heavy-handed.

另一方面,在每次调用mapfilter分配一个全新的数组似乎有些繁琐。

The sequence of calls we made above feels a bit “heavy-handed”, because we only care about the array we get after we make all of our calls to map and filter. The intermediate arrays we generate along the way are throwaways.

上面我们进行的调用序列感觉有些“繁重”,因为我们只关心在完成所有对mapfilter调用之后获得的数组。 我们沿途生成的中间数组是一次性的。

We create them for the sole purpose of providing the next function in the chain with data in the format it expects. We only hang on to the last array we generate. The JavaScript engine eventually garbage collects the intermediate arrays that we built up but didn’t need.

我们创建它们的唯一目的是为链中的下一个功能提供所需格式的数据。 我们只停留在我们生成的最后一个数组上。 JavaScript引擎最终会垃圾收集我们建立但不需要的中间数组。

If you’re using this style of programming to process large lists, this can lead to considerable memory overhead. In other words: We’re trading memory and some incidental code complexity for testability and readability.

如果您使用这种编程方式来处理大型列表,则可能导致相当大的内存开销。 换句话说:为了可测试性和可读性,我们要交换内存和一些附带的代码复杂性。

消除中间阵列 (Eliminating Intermediate Arrays)

For simplicity, let’s consider a sequence of calls to map.

为简单起见,让我们考虑对map的一系列调用。

// See bottom of snippet for `users` list
users
  // Extract important information...
  .map(function (user) {
      // Destructuring: https://jsonplaceholder.typicode.com/users
      return { name, username, email, website } = user
  })
  // Build string... 
  .map(function (reducedUserData) {
    // New object only has user's name, username, email, and website
    // Let's reformat this data for our component
    const { name, username, email, website } = reduceduserdata
    const displayname = `${username} (${name})`
    const contact = `${website} (${email})`

    // Build the string want to drop into our UserCard component
    return `${displayName}\n${contact}`
  })
  // Build components...
  .map(function (displayString) {
      return UserCardComponent(displayString)
  })

// Hoisting so we can keep the important part of this snippet at the top
var users = [
    {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    }
  }
]

To restate the problem: This produces an intermediate, “throwaway” array with every invocation of map. This implies we don’t allocate intermediate arrays if we can find a way to execute all of our processing logic, but only invoke map once.

重述该问题:每次map调用都会产生一个中间的“ throwaway”数组。 这意味着,如果我们能找到执行所有处理逻辑的方法,而只调用一次map ,那么我们就不会分配中间数组。

One way to get away with a single call to map is to do all of our work inside of a single callback.

摆脱对map的一次调用的一种方法是在单个回调中完成所有工作。

const userCards = users.map(function (user) {
    // Destructure user we're interested in...
    const { name, username, email, website } = user

    const displayName = `${username} (${name})`
    const contact = `${website} (${email})`

    // Create display string for our component...
    const displayString = `${displayName}\n${contact}`

    // Build/return UserCard
    return UserCard(displayString)
})

This eliminates intermediate arrays, but this is a step backwards. Throwing everything into a single callback loses the readability and testability benefits that motivated sequenced calls to map in the first place.

这消除了中间数组,但这是倒退的一步。 将所有内容都放入单个回调中会失去可读性和可测试性,而这些好处首先会激发顺序调用map

One way to improve the readability of this version is to extract the callbacks into their own functions, and use them within the call to map, rather than the literal function declarations.

一种提高此版本可读性的方法是将回调提取到它们自己的函数中,并在对map的调用中使用它们,而不是在文字函数声明中使用它们。

const extractUserData = function (user) {
    return { name, username, email, website } = user
}

const buildDisplayString = function (userData) {
    const { name, username, email, website } = reducedUserData
    const displayName = `${username} (${name})`
    const contact = `${website} (${email})`

    return `${displayName}\n${contact}`
}

const userCards = users.map(function (user) {
    const adjustedUserData = extractUserData(user)
    const displayString = buildDisplayString(adjustedUserData)
    const userCard = UserCardComponent(displayString)

    return userCard
})

This is logically equivalent to what we started with, due to referential transparency. But, it’s definitely easier to read, and arguably easier to test.

由于引用透明性,这在逻辑上等同于我们开始时的内容。 但是,它绝对更容易阅读,并且可以说更容易测试。

The real victory here is that this version makes the structure of our processing logic much clearer: sounds like function composition, doesn’t it?

真正的胜利在于,该版本使我们的处理逻辑的结构更加清晰:听起来像函数组合,不是吗?

We can go one step further. Instead of saving the result of each function invocation to a variable, we can simply pass the result of each call directly to the next function in the sequence.

我们可以再走一步。 无需将每个函数调用的结果保存到变量中,我们可以直接将每个调用的结果直接传递给序列中的下一个函数。

const userCards = users.map(function (user) {
    const userCard = UserCardComponent(buildDisplayString(extractUserData(user)))
    return userCard
})

Or, if you like code more terse:

或者,如果您喜欢更简洁的代码:

const userCards = 
  users.map(user => UserCardComponent(buildDisplayString(extractUserData(user))))

组成与融合 (Composition and Fusion)

This restores all of the testability, and some of the readability, of our original chain of map calls. And since we’ve managed to express this transformation with only a single call to map, we’ve eliminated the memory overhead imposed by intermediate arrays.

这样可以恢复原始map调用链的所有可测试性和部分可读性。 并且由于我们仅用一次map调用就设法表达了这种转换,因此我们消除了中间数组带来的内存开销。

We did this by converting our sequence of calls to map, each of which received a “single-purpose” callback, into a single call to map, in which we use a composition of those callbacks.

我们通过将我们调用的顺序做这个map ,每一个获得了“单一用途”的回调,到一个单一的调用map ,在此我们使用这些回调的组成。

This process is called fusion, and allows us to avoid the overhead of intermediate arrays while enjoying the testability and readability benefits of sequenced calls to map.

这个过程称为融合 ,它使我们能够避免中间数组的开销,同时享受顺序调用map的可测试性和可读性的好处。

One last improvement. Let’s take a cue from Python, and be explicit about what we’re doing.

最后一项改进。 让我们从Python中获取一个提示,并明确说明我们在做什么。

const R = require('ramda');

// Use composition to use "single-purpose" callbacks to define a single transformation function
const buildUsercard = R.compose(UserCardComponent, buildDisplayString, extractUserData)

// Generate our list of user components
const userCards = users.map(buildUserCard)

We can write a helper to make this even cleaner.

我们可以写一个帮助程序来使它更加干净。

const R = require('ramda')

const fuse = (list, functions) => list.map(R.compose(...functions))

// Then...
const userCards = fuse(
    // list to transform
    users, 
    // functions to apply
    [UserCardComponent, buildDisplayString, extractUserData]
)

崩溃 (Meltdown)

If you’re like me, this is the part where you start using map and filter just everywhere, even stuff you probably shouldn’t use it for.

如果您像我一样,这就是您开始在任何地方使用mapfilter的部分,甚至您可能不应该使用它的东西。

But the high doesn’t last long with this one. Check this:

但是这一高点不会持续很长时间。 检查一下:

users
  // Today, I've decided I hate the letter a
  .filter(function (user) {
      return user.name[0].toLowerCase() == 'a'
  })
  .map(function (user) {
      const { name, email } = user
      return `${name}'s email address is: ${email}.`
  })

Fusion works fine with a sequence of map calls. It works just as well with a sequence of calls to filter. Unfortunately, it breaks with sequential calls involving both methods. Fusion only works for sequenced calls to one of these methods.

Fusion可通过一系列map调用正常运行。 它与filter的一系列调用同样有效。 不幸的是,它中断了涉及这两种方法的顺序调用。 Fusion仅适用于对这些方法之一的顺序调用。

That’s because they interpret the return values of their callbacks differently. map takes the return value and pushes it into an array, regardless as to what it is.

这是因为它们对回调的返回值的解释不同。 map接受返回值并将其推入数组,无论它是什么。

filter, on the other hand, interprets the truthiness of the callback’s return value. If the callback returns true for an element, it keeps that element. Otherwise, it throws it out.

另一方面, filter解释了回调返回值的真实性。 如果回调针对某个元素返回true ,则保留该元素。 否则,将其丢弃。

Fusion doesn’t work because there’s no way to tell the fused function which callbacks should be used as filters, and which should be used as simple transformations.

融合无法正常工作,因为无法告诉融合的函数哪些回调应被用作过滤器,而哪些回调应被用作简单的转换。

In other words: This approach to fusion only works in the special case of a sequence of calls to map and filter.

换句话说:这种融合方法仅在mapfilter调用序列的特殊情况下有效。

转导 (Transduction)

As we’ve seen, fusion only works for a series of calls only involving map, or only involving filter. This isn’t very helpful in practice, where we’ll typically invoke both. Recall that we were able to express map and filter in terms of reduce.

如我们所见,融合仅适用于仅涉及地图或仅涉及过滤器的一系列调用。 这在实践中不是很有帮助,我们通常会同时调用两者。 回想一下,我们能够通过reduce来表示mapfilter

// Expressing `map` in terms of `reduce`
const map = (list, mapFunction) => {
    const output = list.reduce((transformedList, nextElement) => {
        // use the mapFunction to transform the nextElement in the list 
        const transformedElement = mapFunction(nextElement);

        // add transformedElement to our list of transformed elements
        transformedList.push(transformedElement);

        // return list of transformed elements
        return transformedList;
    }, [])
    // ^ start with an empty list

    return output;
}

// Expressing `filter` in terms of `reduce`
const filter = (list, predicate) => {
    const output = list.reduce(function (filteredElements, nextElement) {
        // only add `nextElement` if it passes our test
        if (predicate(nextElement)) {
            filteredElements.push(nextElement);
        }

        // return the list of filtered elements on each iteration
        return filteredElements;
        }, [])
    })
}

In theory, this means that we can replace our calls to map and then filter with calls to reduce. Then, we’d have a chain of calls involving only reduce, but which implements the same mapping/filtering logic we’re already using.

从理论上讲,这意味着我们可以替换对map的调用,然后使用reduce调用进行filter 。 然后,我们将有一个只涉及reduce的调用链,但是它实现了我们已经在使用的相同映射/过滤逻辑。

From there, we can apply a technique very similar to what we’ve seen with fusion to express our series of reductions in terms of a single function composition.

从那里,我们可以应用一种与融合技术非常相似的技术来表达我们在单一功能组合方面的一系列简化。

步骤1:mapReducer和filterReducer (Step 1: mapReducer and filterReducer)

The first step is to re-express our calls to map and filter in terms of reduce.

第一步是根据reduce来重新表达对mapfilter调用。

Previously, we wrote our own versions of map and filter, which looked like this:

以前,我们编写了自己的mapfilter版本,如下所示:

const mapReducer = (list, mapFunction) => {
    const output = list.reduce((transformedList, nextElement) => {
        // use the mapFunction to transform the nextElement in the list 
        const transformedElement = mapFunction(nextElement);

        // add transformedElement to our list of transformed elements
        transformedList.push(transformedElement);

        // return list of transformed elements
        return transformedList;
    }, [])
    // ^ start with an empty list

    return output;
}

const filterReducer = (list, predicate) => {
    const output = list.reduce(function (filteredElements, nextElement) {
        // only add `nextElement` if it passes our test
        if (predicate(nextElement)) {
            filteredElements.push(nextElement);
        }

        // return the list of filtered elements on each iteration
        return filteredElements;
        }, [])
    })
}

We used these to demonstrate the relationship between reduce and map/filter, but we need to make some changes if we want to use this in reduce chains.

我们使用这些来演示reducemap / filter之间的关系,但是如果要在reduce链中使用它,我们需要进行一些更改。

Let’s start by removing those calls to reduce:

让我们通过删除这些电话开始reduce

const mapReducer = mapFunction => (transformedList, nextElement) => {
    const transformedElement = mapFunction(nextElement);

    transformedList.push(transformedElement);

    return transformedList;
}

const filterReducer = predicate => (filteredElements, nextElement) => {
    if (predicate(nextElement)) {
        filteredElements.push(nextElement);
    }

    return filteredElements;
}

Earlier, we filtered and mapped an array of user names. Let’s start rewriting that logic with these new functions to make all this a little less abstract.

之前,我们过滤并映射了一组user名。 让我们开始使用这些新功能来重写该逻辑,以使所有这些变得不太抽象。

// filter's predicate function
function removeNamesStartingWithA (user) {
    return user.name[0].toLowerCase() != 'a'
}

// map's transformation function
function createUserInfoString (user) {
    const { name, email } = user
    return `${name}'s email address is: ${email}.`
}

users
  .reduce(filterReducer(removeNamesStartingWithA), [])
  .reduce(mapReducer(createUserInfoString), [])

This produces the same result as our previous filter/map chain.

产生的结果与之前的filter / map链相同。

This is quite a few layers of indirection involved. Take some time to step through the above snippet before moving on.

这涉及许多间接层。 在继续之前,请花一些时间逐步浏览上述片段。

步骤2:概括折叠功能 (Step 2: Generalizing Our Folding Function)

Take another look at mapReducer and filterReducer.

再看看mapReducerfilterReducer

const mapReducer = mapFunction => (transformedList, nextElement) => {
    const transformedElement = mapFunction(nextElement);

    transformedList.push(transformedElement);

    return transformedList;
}

const filterReducer = predicate => (filteredElements, nextElement) => {
    if (predicate(nextElement)) {
        filteredElements.push(nextElement);
    }

    return filteredElements;
}

Rather than hard-code transformation or predicate logic, we allow the user to pass in mapping and predicate functions as arguments, which the partial applications of mapReducer and filterReducer remember due to closure.

而不是硬代码转换或谓词逻辑,我们允许用户传入映射和谓词函数作为参数,由于闭filterReducermapReducerfilterReducer的部分应用程序会记住这些参数。

This way, we can use mapReducer and filterReducer as “backbones” in building arbitrary reduction chains by passing the predicate or mapFunction appropriate for our use case.

这样,我们可以通过传递适合我们用例的predicatemapFunction在构建任意归约链时将mapReducerfilterReducer用作“主干”。

If you look closely, you’ll notice that we still make explicit calls to push in both of these reducers. This is important, because push is the function that allows us to combine, or reduce, two objects into one:

如果你仔细观察,你会发现,我们还进行显式调用以push在这两个减速器。 这很重要,因为push是允许我们将两个对象组合或缩小为一个的函数:

// Object 1...
const accumulator = ["an old element"];

// Object 2...
const next_element = "a new element";

// A single object that combines both! Eureka!
accumulator.push(next_element);

// ["an old element", "a new element"]
console.log(accumulator)

Recall that combining elements like this is the whole point of using reduce in the first place.

回想一下,像这样组合元素首先是使用reduce的全部要点。

If you think about it, push isn’t the only function we can use to do this. We could use unshift, instead:

如果您考虑一下, push不是我们可以用来执行此操作的唯一功能。 我们可以使用unshift

// Object 1...
const accumulator = ["an old element"];

// Object 2...
const next_element = "a new element";

// A single object that combines both! Eureka!
accumulator.unshift(next_element);

// ["a new element", "an old element"]
console.log(accumulator);

As written, our reducers lock us into using push. If we wanted to unshift, instead, we’d have to re-implement mapReducer and filterReducer.

如所写,减速器将我们锁定为使用push 。 如果我们想unshift ,相反,我们不得不重新实现mapReducerfilterReducer

The solution is abstraction. Rather than hard-code push, we’ll let the user pass the function they want to use to combine elements as an argument.

解决方案是抽象。 除了让用户进行硬编码push ,我们还让用户传递他们想要使用的函数来将元素组合为参数。

const mapReducer = combiner => mapFunction => (transformedList, nextElement) => {
    const transformedElement = mapFunction(nextElement);

    transformedList = combiner(transformedList, transformedElement);

    return transformedList;
}

const filterReducer = combiner => predicate => (filteredElements, nextElement) => {
    if (predicate(nextElement)) {
        filteredElements = combiner(filteredElements, nextElement);
    }

    return filteredElements;
}

We use it like this:

我们这样使用它:

// push element to list, and return updated list
const pushCombiner = (list, element) => {
    list.push(element);
    return list;
}

const mapReducer = mapFunction => combiner => (transformedList, nextElement) => {
    const transformedElement = mapFunction(nextElement);

    transformedList = combiner(transformedList, transformedElement);

    return transformedList;
}

const filterReducer = predicate => combiner => (filteredElements, nextElement) => {
    if (predicate(nextElement)) {
        filteredElements = combiner(filteredElements, nextElement);
    }

    return filteredElements;
}

users
  .reduce(
      filterReducer(removeNamesStartingWithA)(pushCombiner), [])
  .reduce(
      mapReducer(createUserInfoString)(pushCombiner), [])

步骤3:转导 (Step 3: Transduction)

At this point, everything’s in place for our final trick: Composing these transformations to fuse those chained calls to reduce. Let’s see it in action first, and then review.

至此,一切都准备就绪,这是我们最终的窍门:组合这些转换以融合那些链接在一起的reduce调用。 让我们先看看它的作用,然后再回顾。

const R = require('ramda');

// final mapReducer/filterReducer functions
const mapReducer = mapFunction => combiner => (transformedList, nextElement) => {
    const transformedElement = mapFunction(nextElement);

    transformedList = combiner(transformedList, transformedElement);

    return transformedList;
}

const filterReducer = predicate => combiner => (filteredElements, nextElement) => {
    if (predicate(nextElement)) {
        filteredElements = combiner(filteredElements, nextElement);
    }

    return filteredElements;
}

// push element to list, and return updated list
const pushCombiner = (list, element) => {
    list.push(element);
    return list;
}

// filter's predicate function
const removeNamesStartingWithA = user => {
    return user.name[0].toLowerCase() != 'a'
}

// map's transformation function
const createUserInfoString = user => {
    const { name, email } = user
    return `${name}'s email address is: ${email}.`
}

// use composition to create a chain of functions for fusion (!)
const reductionChain = R.compose(
    filterReducer(removeNamesStartingWithA)
    mapReducer(createUserInfoString),
)

users
  .reduce(reductionChain(pushCombiner), [])

We can go one further by implementing a helper function.

我们可以通过实现辅助函数来进一步发展。

const transduce = (input, initialAccumulator, combiner, reducers) => {
    const reductionChain = R.compose(...reducers);
    return input.reduce(reductionChain(combiner), initialAccumulator)
}

const result = transduce(users, [], pushCombiner, [
    filterReducer(removeNamesStartingWithA)
    mapReducer(createUserInfoString),
]);

结论 (Conclusion)

There are more solutions to almost any problem than anyone could ever enumerate; the more of them you meet, the clearer you’ll think about your own, and the more fun you’ll have doing so.

几乎任何问题都有比任何人都难以企及的更多解决方案。 您遇到的人越多,您对自己的想法就会越清晰,并且这样做会越有乐趣。

I hope meeting Fusion and Transduction piques your interest, helps you think more clearly, and, ambitious as it is, was at least a little bit fun.

我希望与Fusion and Transduction的会面会激起您的兴趣,帮助您更清晰地思考,而且雄心勃勃,至少有点有趣。

翻译自: https://www.digitalocean.com/community/tutorials/javascript-functional-programming-explained-fusion-transduction


http://www.niftyadmin.cn/n/3649690.html

相关文章

【javaEE面试题(四)线程不安全的原因】【1. 修改共享数据 2. 操作不是原子性 3. 内存可见性 4. 代码顺序性】

4. 多线程带来的的风险-线程安全 (重点) 4.1 观察线程不安全 static class Counter {public int count 0;void increase() {count;} } public static void main(String[] args) throws InterruptedException {final Counter counter new Counter();Thread t1 new Thread(()…

CentOS 7安装谷歌浏览器

一、安装谷歌浏览器 1、使用root登录终端 su root2、配置下载yum源 cd /etc/yum.repos.d vim google-chrome.repo添加以下内容 [google-chrome] namegoogle-chrome baseurlhttp://dl.google.com/linux/chrome/rpm/stable/$basearch enabled1 gpgcheck1 gpgkeyhttps://dl-ss…

SymbianOS Error -3606问题解决了

CMNET"SymbianOsError -3606"“KErrGenConnDatabaseDefaultUndefined -3606 "No Internet accounts have been set up. Set up an account in Control panel." ”CMNET

web应用程序体系结构_为不同的操作系统和体系结构构建Go应用程序

web应用程序体系结构In software development, it is important to consider the operating system and underlying processor architecture that you would like to compile your binary for. Since it is often slow or impossible to run a binary on a different OS/archit…

我的个人博客正式关联第三方博客平台

第三方博客平台简介 第三方博客指的是不要求自己有域名,空间,服务器,仅在大型门户网址注册就可运行的博客平台。 这类博客有新浪,搜狐,和讯,网易等。第三方博客现在已经成为更多网络爱好者发布自己心情&am…

[JavaME]手机申请移动分配的动态IP?(2)

[JavaME]手机申请移动分配的动态IP(2)?先用ServerSocketConnection.open然后向某服务器询问手机自己的IP? Hi,继续上回的讨论《[JavaME]手机是否能够申请到动态IP?》。上回说到申请动态IP的调试顺序可能反了,经过今天…