• Functional-light-js
  • Kyle Simpson is the author of You-Dont-Know-JS

This is a pure project with Hujiang’s blood flowing: earnest is the most solid pillar of HTML; Sharing is the most shining glance in CSS; Summary is the most rigorous logic in JavaScript. After hammering and honing, the Chinese version of this book is achieved. This book contains the essence of functional programming, hoping to help you learn functional programming on the road to go more smoothly. finger heart

Team of Translators (in no particular order) : Ahi, Blueken, Brucecham, CFanlife, Dail, Kyoko-DF, L3VE, Lilins, LittlePineapple, MatildaJin, Holly, Pobusama, Cherry, radish, VavD317, Vivaxy, MOE MOE, zhouyao

JavaScript lightweight functional programming

Appendix A: Do you transduced (ii)

Combinatorial Cremation

This is the trickiest step. So read slowly and carefully.

Let’s look at the listCombination(..) What is passed to the Currization function:

var x = curriedMapReducer( strUppercase );
var y = curriedFilterReducer( isLongEnough );
var z = curriedFilterReducer( isShortEnough );
Copy the code

Look at the three intermediate functions x(..) , y (..) And z (..) . Each function expects a single combinatorial function and produces a reducer function.

Remember, if we wanted an independent reducer of all this, we could do this:

var upperReducer = x( listCombination );
var longEnoughReducer = y( listCombination );
var shortEnoughReducer = z( listCombination );
Copy the code

But if you call y of z, what do you get? When passing z to y(..) Call instead of combinationFn(..) What happens? The internal reducer function returned looks like this:

function reducer(list,val) {
	if (isLongEnough( val )) return z( list, val );
	return list;
}
Copy the code

See z (..) Is there a call in there? This should look wrong because z(..) Functions should only take one argument (combinationFn(..)) ), instead of two arguments (list and val). This doesn’t match the requirements. I can’t.

Let’s look at y(listCombination). We’ll break it down into two distinct steps:

var shortEnoughReducer = z( listCombination );
var longAndShortEnoughReducer = y( shortEnoughReducer );
Copy the code

We created shortEnoughReducer(..) And then use it as a combinationFn(..) Pass to y (..) To generate the longAndShortEnoughReducer (..) . Read it several times until you understand.

Now think about it: shortEnoughReducer(..) And longAndShortEnoughReducer (..) What is the internal structure of the Can you imagine?

// shortEnoughReducer, from z(..) :
function reducer(list,val) {
	if (isShortEnough( val )) return listCombination( list, val );
	return list;
}

// longAndShortEnoughReducer, from y(..) :
function reducer(list,val) {
	if (isLongEnough( val )) return shortEnoughReducer( list, val );
	return list;
}
Copy the code

You see shortEnoughReducer (..) Replace the longAndShortEnoughReducer (..) Inside listCombination (..) Is the location available? Why does that work?

Because of the reducer (..) “Shapes” and listCombination(..) It’s the same shape. In other words, reducer can be used as another combinatorial function of Reducer; That’s how they’re put together! listCombination(..) Function acts as a combinatorial function of the first Reducer, which in turn acts as a combinatorial function of the next reducer, and so on.

We use several different values to test our longAndShortEnoughReducer (..) :

longAndShortEnoughReducer( [], "nope" );
/ / []

longAndShortEnoughReducer( [], "hello" );
// ["hello"]

longAndShortEnoughReducer( [], "hello world" );
/ / []
Copy the code

longAndShortEnoughReducer(..) Values that are not long enough and not short enough are filtered, and it performs both filters in the same step. This is a reducer!

Take some time to digest it.

Now, put x(..) Add a reducer producer:

var longAndShortEnoughReducer = y( z( listCombination) );
var upperLongAndShortEnoughReducer = x( longAndShortEnoughReducer );
Copy the code

As upperLongAndShortEnoughReducer (..) As the name suggests, it performs all three steps at once – a map and two filters! It looks like this on the inside:

// upperLongAndShortEnoughReducer:
function reducer(list,val) {
	return longAndShortEnoughReducer( list, strUppercase( val ) );
}
Copy the code

A string of type val is passed in by strUppercase(..). Converted to uppercase, and then passed to the longAndShortEnoughReducer (..) . This function only adds val to the array if it is long enough and short enough. Otherwise the array stays the same.

I spent weeks thinking and analyzing this sideshow operation. So don’t worry, if you need to study it, reread it a few (or a dozen) times. Take your time.

Now to verify:

upperLongAndShortEnoughReducer( [], "nope" );
/ / []

upperLongAndShortEnoughReducer( [], "hello" );
// ["HELLO"]

upperLongAndShortEnoughReducer( [], "hello world" );
/ / []
Copy the code

This Reducer successfully combined and map and two filters, great!

Let’s review what we’ve done so far:

var x = curriedMapReducer( strUppercase );
var y = curriedFilterReducer( isLongEnough );
var z = curriedFilterReducer( isShortEnough );

var upperLongAndShortEnoughReducer = x( y( z( listCombination ) ) );

words.reduce( upperLongAndShortEnoughReducer, [] );
// ["WRITTEN","SOMETHING"]
Copy the code

It’s already cool, but we can make it better.

x(y(z( .. ) (is a combination. We can skip the intermediate x/y/z variable names and express the combination as follows:

var composition = compose(
	curriedMapReducer( strUppercase ),
	curriedFilterReducer( isLongEnough ),
	curriedFilterReducer( isShortEnough )
);

var upperLongAndShortEnoughReducer = composition( listCombination );

words.reduce( upperLongAndShortEnoughReducer, [] );
// ["WRITTEN","SOMETHING"]
Copy the code

Let’s consider the flow of “data” in this combinatorial function:

  1. listCombination(..) Passed as a composite function, construct isShortEnough(..) Reducer of filters.

  2. The resulting reducer function is then passed in as a combinational function and proceeds to construct isShortEnough(..) Reducer of filters.

  3. Finally, the resulting reducer function is passed in as a combinatorial function to construct strUppercase(..) Reducer of the map.

In the previous clip, composition(..) Is a combinatorial function that is expected to form a reducer; The composition (..) One special tag: token. The reducer that produces the combination is provided to the hand:

// TODO: Check whether the reducer is generated or the reducer itself

var transducer = compose(
	curriedMapReducer( strUppercase ),
	curriedFilterReducer( isLongEnough ),
	curriedFilterReducer( isShortEnough )
);

words
.reduce( transducer( listCombination ), [] );
// ["WRITTEN","SOMETHING"]
Copy the code

Note: We should take a closer look at the compose(..) in the previous two fragments. Order. This is a little hard to understand. Recall that in our original example, we started with map(strUppercase), then filter(isLongEnough), and finally filter(isShortEnough); These operations are actually executed in this order. But in chapter 4, we learn that compose(..) Usually run in reverse order. So why don’t we need to reverse the order here to get the same desired result? CombinationFn (..) from each reducer The abstraction reverses the order of operations. So counterintuitively, when composing a Tranducer, you just need to do it in the actual order!

List composition: pure and impure

Let’s take a look at our listCombination(..) Implementation of combinatorial functions:

function listCombination(list,val) {
	return list.concat( [val] );
}
Copy the code

Although this approach is pure, it has a negative impact on performance. First, it creates a temporary array to wrap around val. Then, the concat (..) Method creates a brand new array to concatenate the temporary array. Many arrays are created and destroyed at each step, which is not only bad for the CPU, but also drains GC memory.

Here are the better but impure versions:

function listCombination(list,val) {
	list.push( val );
	return list;
}
Copy the code

Consider listCombination(..) alone There is no doubt that it is impure, which is usually what we want to avoid. But we should consider a larger context.

listCombination(..) It’s not a function that we completely interact with. We don’t use it directly anywhere in the program, just in the midst of bleeding.

Back in Chapter 5, the goal of defining pure functions to reduce side effects is limited only to the application API level. For the underlying implementation, it is possible to become impure within a function for performance as long as it is pure externally.

listCombination(..) More on the internal implementation details of the transformation. In fact, it is usually supplied by transducing repositories! Not the top-level methods that interact in your program.

Bottom line: I think even using listCombination(..) The best performance but impure version is also perfectly acceptable. Just make sure you use code comments to note that it’s not pure!

Optional combination

So far, this is what we have with the transformation:

words
.reduce( transducer( listCombination ), [] )
.reduce( strConcat, "" );
// Write something
Copy the code

That’s pretty good, but there’s one last trick we’re hiding. Frankly, I think this part makes all the hard work you’ve done so far worth it.

We can somehow do this with just one reduce(..) To “combine” the two reduce(..) ? Unfortunately, we can’t put strConcat(..) Added to the compose (…) In the call; Its “shape” does not apply to that combination.

But let’s take a look at these two features:

function strConcat(str1,str2) { return str1 + str2; }

function listCombination(list,val) { list.push( val ); return list; }
Copy the code

If you look closely, you can see how the two functions are interchangeable. They run on different data types, but conceptually they are also the same: combine two values into one.

In other words, strConcat(..) Is a combinatorial function!

This means that if our end goal is to get string concatenations instead of arrays, we can use it instead of listCombination(..) :

words.reduce( transducer( strConcat ), "" );
// Write something
Copy the code

Boom! This is Transducing.

The last

Take a deep breath. That’s a lot to digest.

Let’s clear our minds and focus on how to use transformations in our programs rather than how it works.

Recalling the helper function we defined earlier, let’s rename it for clarity:

var transduceMap = curry( function mapReducer(mapperFn,combinationFn){
	return function reducer(list,v){
		returncombinationFn( list, mapperFn( v ) ); }; });var transduceFilter = curry( function filterReducer(predicateFn,combinationFn){
	return function reducer(list,v){
		if (predicateFn( v )) return combinationFn( list, v );
		returnlist; }; });Copy the code

Remember how we used them:

var transducer = compose(
	transduceMap( strUppercase ),
	transduceFilter( isLongEnough ),
	transduceFilter( isShortEnough )
);
Copy the code

transducer(..) Still requires a combination function (such as listCombination(..) Or strConcat (..) ) to generate a pass to reduce(..) Transduce – Reducer function (along with the initial values).

But to better express all these transformation steps, let’s do transduce(..) Tools to do these steps for us:

function transduce(transducer,combinationFn,initialValue,list) {
	var reducer = transducer( combinationFn );
	return list.reduce( reducer, initialValue );
}
Copy the code

Here is our running example, combed as follows:

vartransducer = compose( transduceMap( strUppercase ), transduceFilter( isLongEnough ), transduceFilter( isShortEnough ) );  transduce( transducer, listCombination, [], words );// ["WRITTEN","SOMETHING"]

transduce( transducer, strConcat, "", words );
// Write something
Copy the code

Nice, huh! See listCombination (..) And strConcat (..) Can functions be used interchangeably with combinatorial functions?

Transducers.js

Finally, we’ll illustrate the example we’re running, using the sensors- JS library (github.com/cognitect-l…) :

var transformer = transducers.comp(
	transducers.map( strUppercase ),
	transducers.filter( isLongEnough ),
	transducers.filter( isShortEnough )
);

transducers.transduce( transformer, listCombination, [], words );
// ["WRITTEN","SOMETHING"]

transducers.transduce( transformer, strConcat, "", words );
// WRITTENSOMETHING
Copy the code

It looks almost the same.

Note: The code snippet above uses transformers.comp(..) , because the library provides the API, but in this case, we’re drawn from compose(..) in Chapter 4. It will produce the same result. In other words, the combination itself is not a bleeding sensitive operation.

The combined functions in this section are called transformer, not transducer. That’s because if we call Transformer (listCombination) (or Transformer (strConcat)) directly, then we don’t get an intuitive Transduce -reducer function like we did before.

transducers.map(..) And transducers. The filter (..) Is a special auxiliary function, which can transform the normal assertion function or mapping function into a function suitable for generating special transformation objects (including reducer function); The library uses these transformation objects for transformations. The additional functionality of this transformation object abstraction is beyond what we will explore, see the library’s documentation for more information.

Due to the transformer (..) Produces a transform object instead of a typical binary Transduce -reducer function, which also provides toFn(..) Reduce (..) that ADAPTS transform objects to local arrays Methods:

words.reduce(
	transducers.toFn( transformer, strConcat ),
	""
);
// WRITTENSOMETHING
Copy the code

into(..) Is another provided helper function that automatically selects the default combination function based on the type of the specified null/initial value:

transducers.into( [], transformer, words );
// ["WRITTEN","SOMETHING"]

transducers.into( "", transformer, words );
// WRITTENSOMETHING
Copy the code

Internal Transduce (..) when specifying an empty array [] Implemented using a default function, like our listCombination(..) . But when specifying an empty string “”, we use strConcat(..) like ours. This way. It’s very cool!

As you can see, the Transducers – JS libraries make switching very easy. We can harness the power of this technology very effectively without getting bogged down in the tedious process of defining all these intermediate converter production tools.

conclusion

Duce is Transduce by reducing. More specifically, Transduer is a combinable reducer.

We use transformations to combine adjacent maps (..). And the filter (..) And reduce (..) Operation. We start with map(..) And the filter (..) To reduce (..) , and then abstracts the common combinatorial operations to create a consistent Reducer generation function that is easy to assemble.

Transducing primarily improves performance, especially if used in asynchronous observables.

But more generally, this is a more declarative approach that we use for functions that can’t be put together directly. Otherwise these functions will not be combined directly. If you use this technique just as well as all the others in this book, your code will be clearer and easier to read! Using hand to perform a single reduce(..) Call ratio trace multiple Reduce (..) Calls are easier to understand.

* * 【 】 in the previous chapter translation serial | see appendix: Transducing (on) – “JavaScript lightweight functional programming” | “you don’t know the JS” companion piece * *

IKcamp’s original new book “Mobile Web Front-end Efficient Development Combat” has been sold on Amazon, JD.com and Dangdang.

IKcamp website: www.ikcamp.com IKcamp produced | the whole network latest | wechat small program | based on the latest version 1.0 developer tools of the beginner and intermediate training tutorial to share “iKcamp produced | based on Koa2 build Node.js actual combat project tutorial” contains: article, video, source code


In 2019, iKcamp’s original new book Koa and Node.js Development Actual Combat has been sold on JD.com, Tmall, Amazon and Dangdang!