• 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 B: Modest Monad

First, a confession: I didn’t know much about Monad before I started writing this. I made a lot of mistakes trying to confirm things. If you don’t believe me, check out this chapter’s commit history in the book’s Git repository!

I have covered all Monad topics in this book. Like my book, this is part of every developer’s journey to learn functional programming.

While other books on functional programming almost all start with Monad, we give it a brief introduction and pretty much end the book with it. I really haven’t had too many problems with Monad in lightweight functional programming, which is why this article is all the more valuable. But that’s not to say Monad isn’t useful or universal — on the contrary, it’s useful and popular.

There’s a joke in functional programming that almost everyone has to write about Monad in their article or blog. It’s like a ritual to write about it. Over the years, Monad has been described as burritos, Onions, and all sorts of weird abstractions. I won’t do it again!

A Monad is merely a monoid in the category of endofunctor

We started with that quote, so it seemed appropriate to turn the conversation to that quote. But no, we’re not talking about Monad or Endofunctor or category theory. This quote is not only mystifying but also flashy.

I just hope that through our discussion you are not afraid of the term Monad or the concept — I was for a long time — and know what the term is when you see it. You might, just might, use them correctly.

type

There is one huge area of interest in functional programming that this book largely leaves completely: type theory. I’m not going to go into genre theory, and frankly, I don’t have the ability to go into genre theory, and even if I did, it would be thankless.

But I would say that Monad is basically a value type.

The number 42 has a value type (number) with the characteristics and functionality we rely on. The string “42” may look similar, but it serves a different purpose in programming.

In object-oriented programming, when you have a set of data (or even a single discrete value) and you want to attach some behavior to it, you create an object or class to represent “type”. The instance then becomes a member of that type. This practice is often referred to as “data structure.”

I’m going to use the concept of data structures very broadly, and I conclude that we might find it useful in programming when we define a set of behaviors and constraints for a particular value and bind those characteristics to a single abstraction along with the value. This way, when we use one or more of these values in our programming, their behavior will appear naturally and make them easier to work with. Conveniently, your code is more descriptive and declarative to the reader.

Monad is a data structure. It’s a type. It is a set of specific behaviors that make processing a value predictable.

Recalling chapter 8, we talked about functors: a map-like utility function that includes a value and performs operations on the data that makes up the functor. Monad is a functor that contains some additional behavior.

Loose interface

In fact, Monad is not a single data type, but rather a collection of related data types. It is an interface that is implemented in different ways depending on the needs of different values. Each implementation is a different type of Monad.

For example, you might read “Identity Monad”, “IO Monad”, “Maybe Monad”, “Either Monad”, or a variety of other words. Each of them has a basic Monad behavior definition, but it inherits or overrides the interaction based on each different type of Monad use case.

But it is more than just an interface, because it is not just an implementation of some OF Monad’s API methods that make objects. The assurance that these methods interact is required and monadic. These well-known constants are essential to improve readability with Monad; In addition, it is a special data structure that the reader must read in full to understand.

In fact, there isn’t even a consensus on the names of these Monad methods and how real interfaces are authorized; Monad is more of a loose interface. Some call these methods bind(..) , some call it chain(..) Others call it flatMap(..) , and so on.

So, Monad is an object data structure and has sufficient methods (almost any name or sort) to satisfy at least the main behavioral requirements defined by Monad. Each Monad expands differently based ona minimum number of methods. However, because they both overlap in behavior, it is still straightforward and manageable to use the two different Monads together.

In a sense, Monad is more like an interface.

Maybe

In functional programming, it is common to cover Monad like Maybe. In fact, Maybe Monad is a combination of two other simpler monads: Just and Nothing.

Since Monad is a type, you might think that we should define Maybe as a class to be instantiated. This is a valid approach, but it introduces problems with the this binding, so I won’t discuss them here; Instead, I’m going to use a simple implementation of functions and objects.

Here is the simplest implementation of Maybe:

var Maybe = { Just, Nothing, of/* Also called: unit, pure */: Just };

function Just(val) {
	return { map, chain, ap, inspect };

	/ / * * * * * * * * * * * * * * * * * * * * *

	function map(fn) { return Just( fn( val ) ); }
	// Also called: bind, flatMap
	function chain(fn) { return fn( val ); }
	function ap(anotherMonad) { return anotherMonad.map( val ); }

	function inspect() {
		return `Just(${ val }) `; }}function Nothing() {
	return { map: Nothing, chain: Nothing, ap: Nothing, inspect };

	/ / * * * * * * * * * * * * * * * * * * * * *

	function inspect() {
		return "Nothing"; }}Copy the code

Note: inspect (..) Method is used only in our example. From Monad’s point of view, it doesn’t make any sense.

If most of it doesn’t make sense now, don’t worry. We’ll focus more on what we can do with it, rather than delving too deeply into the design details and theories behind Monad.

All monads like, any containing Just(..) Monad instances of Nothing() and Nothing() have map(..). , chain (..) (also called the bind (..) Or flatMap (..) ) and ap (..) Methods. The purpose of these methods and their behavior is to provide a standardized way for multiple Monad instances to work together. You will notice that whether Just(..) What value does the instance get, Just(..) Instances don’t change it. All methods create a new Monad instance rather than changing it.

Maybe is a combination of these two monads. If a value is non-empty, it is Just(..). An instance of the; If the value is null, it is an instance of Nothing(). Note that it’s up to your code to determine what “empty” means, we’re not going to enforce it. More on this in the next section.

But the value of Monad is that whether we have Just(..) Instance or Nothing() instance, we use the same method. The Nothing() instance has empty operation definitions for all methods. So if a Monad instance is present in a Monad operation, it will short-circuiting the Monad operation.

The Maybe abstraction implicitly encapsulates the duality of action and no-action.

Maybe different

Many implementations of JavaScript Maybe Monad include null and undefined checks (usually in map(..)). In), if it is empty, the feature behavior of the Monad is skipped. In fact, Maybe is claimed to be valuable because it automatically encapsulates null-checking in a way that short-circuits its characteristic behavior.

This is a typical description of Maybe:

/ / instead of the unsteady ` console. The log (.. SomeObj. Something else entirely) ` :

Maybe.of( someObj )
.map( prop( "something" ) )
.map( prop( "else" ) )
.map( prop( "entirely" ) )
.map( console.log );
Copy the code

In other words, if we get a null or undefined value at any point in the chain operation, Maybe will intelligently switch to null mode — it’s now a Nothing() Monad instance! — Stop the rest of the chain operation. Nested property access can safely throw JS exceptions if some properties are missing or empty. This is very cool and very practical.

However, the Maybe we implemented this way is not a pure Monad.

The core idea of Monad is that it must be valid for all values and cannot do any checks on values — even null checks. So these other implementations are shortcuts for convenience. It’s irrelevant. But when learning something, you should first learn it in its purest form before learning the more complex rules.

The Maybe Monad implementation I provided earlier is different from the other Maybe in that it has no vacancy check. Also, we use Maybe as Just(..) Loosely combined with Nothing().

Wait, if we don’t have an automatic short circuit, how does Maybe work? ! ? That seems to be the whole point of it.

Don’t worry, we can provide a simple null check externally, and Maybe Monad’s other short-circuiting behavior will work just fine. . Before you can do some someObj. Something else, entirely attribute nested, but we can do the “right” :

function isEmpty(val) {
	return val === null || val === undefined;
}

var safeProp = curry( function safeProp(prop,obj){
	if (isEmpty( obj[prop] )) return Maybe.Nothing();
	returnMaybe.of( obj[prop] ); }); Maybe.of( someObj ) .chain( safeProp("something" ) )
.chain( safeProp( "else" ) )
.chain( safeProp( "entirely" ) )
.map( console.log );
Copy the code

We designed a safeProp(..) for null value checking. Function, and selects Nothing() Monad instance. Or wrap the value in Just(..) In the example (through maybe.of (..)) ). Then we use chain(..) Replace the map (..) , it knows how to “unroll” safeProp(..) Return Monad.

When we hit a null value, we get the same series of short circuits. We just take that logic out of Maybe.

Regardless of the type of Monad returned, our map(..) And the chain (..) Methods have constant and predictable feedback, and this is the benefit of Monad, especially Maybe Monad. Isn’t that cool?

Humble

Now that we know a little bit more about Maybe and what it does, I’m going to add a little twist and a little bit of humor by designing the Maybe + Humble Monad. Technically, Humble(..) Not a Monad, but a factory function that produces instances of Maybe Monad.

Humble is a data structure wrapper that uses Maybe to track the digital state of egoLevel. Specifically, Humble(..) Generated Monad instances are executed only if their own level value is low enough (less than 42) to be considered Humble; Otherwise, it is a Nothing() empty operation. That really sounds like Maybe!

Here’s a Maybe + Humble Monad factory function:

function Humble(egoLevel) {
	// Accepts any number greater than or equal to 42
	return! (Number( egoLevel ) >= 42)? Maybe.of( egoLevel ) : Maybe.Nothing(); }Copy the code

You may notice that this factory function is a bit like safeProp(..) Because, it uses a condition to decide whether to choose Maybe Just(..) Or Nothing ().

Let’s look at an example of basic usage:

var bob = Humble( 45 );
var alice = Humble( 39 );

bob.inspect();							// Nothing
alice.inspect();						// Just(39)
Copy the code

If Alice won a big prize, would she be proud of herself now?

function winAward(ego) {
	return Humble( ego + 3 );
}

alice = alice.chain( winAward );
alice.inspect();						// Nothing
Copy the code

Humble(39 + 3) creates a chain(..) Return Nothing() Monad instance, so now Alice no longer qualifies as Humble.

Now, let’s use some Monad:

var bob = Humble( 41 );
var alice = Humble( 39 );

var teamMembers = curry( function teamMembers(ego1,ego2){
	console.log( `Our humble team's egos: ${ego1} ${ego2}`); }); bob.map( teamMembers ).ap( alice );// Humble queue: 41 39
Copy the code

Because of the teamMembers (..) It’s currified, Bob.map (..) The call to Bob’s own level (41) is passed in, and an instance of Monad is created wrapped by the rest of the methods. The AP (Alice) called in this Monad calls Alice.map (..) And passed to the function from Monad. The effect of this is that the value of Monad is already provided to teamMembers(..) Function, and print out the displayed results.

However, if a Monad or two monads are actually instances of Nothing(because their own level values are too high) :

var frank = Humble( 45 );

bob.map( teamMembers ).ap( frank );

frank.map( teamMembers ).ap( bob );
Copy the code

teamMembers(..) It is never called (and no message is printed), because frank is an instance of Nothing(). That’s where Maybe Monad comes in, our Humble(..) The factory function allows us to choose according to our level. Wow!!!!

Humility

Here’s another example of how the Maybe + Humble data structure behaves:

function introduction() {
	console.log( "I'm just a learner like you! :)" );
}

var egoChange = curry( function egoChange(amount,concept,egoLevel) {
	console.log( `${amount > 0 ? "Learned" : "Shared"} ${concept}. ` );
	returnHumble( egoLevel + amount ); });var learn = egoChange( 3 );

var learner = Humble( 35 );

learner
.chain( learn( "closures" ) )
.chain( learn( "side effects" ) )
.chain( learn( "recursion" ) )
.chain( learn( "map/reduce" ) )
.map( introduction );
// Learn closures
// Learning side effects
// Rest recursion
Copy the code

Unfortunately, the learning process seems to have shortened. I find that learning a lot of stuff without sharing it with others tends to overinflate your ego, which is not good for your skills.

Let’s try a better approach:

var share = egoChange( 2 - );

learner
.chain( learn( "closures" ) )
.chain( share( "closures" ) )
.chain( learn( "side effects" ) )
.chain( share( "side effects" ) )
.chain( learn( "recursion" ) )
.chain( share( "recursion" ) )
.chain( learn( "map/reduce" ) )
.chain( share( "map/reduce" ) )
.map( introduction );
// Learn closures
// Share closure
// Learning side effects
// Share side effects
// Learn recursion
// Share recursion
/ / map/reduce to learn
/ / share map/reduce
// I'm just a learner like you:
Copy the code

Share in learning. Is the best way to learn more and be able to learn better.

conclusion

Having said that, what is Monad?

Monad is a value type, an interface, and an object data structure that encapsulates behavior.

But none of these definitions are useful. Here’s a better explanation: Monad is a way to organize behavior around a value in a more declarative way.

As with the rest of the book, use Monad where it’s useful, and don’t use them just because everyone talks about them in functional programming. Monad is not a panacea, but it does provide some useful utility functions.

* * 【 】 in the previous chapter translation serial | see appendix: Transducing (under) – “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!