In the last article, we discussed common functional programming cases. Some students complained that they did not cover the underlying concepts. They wanted to know what Monad is. With that in mind, let’s explore.

In functional programming, Monad is an abstraction of a structured program, which we understand in three parts.

  • Monad definition
  • Monad usage scenarios
  • Monad explains in one sentence

Monad definition

According to Wikipedia, Monad consists of the following three parts:

  • A type constructor (M) that builds unary typesM<T>.
  • A type conversion function (return or unit) that can load a primitive value into M.
    • unit(x) : T -> M T
  • Bind, a combinative function, can take a value from an M instance, put it into a function, and finally get a new M instance.
    • M<T>performT-> M<U>generateM<U>

In addition, it follows a few rules:

  • Unit element rules, usually implemented by the unit function.
  • Associative rules, usually implemented by bind.

Identity element: A special element of a set, associated with binary operations in that set. When units are combined with other elements, they do not change those elements.

The identity element of multiplication is 1, any number x 1 = any number itself, and 1 x any number = any number itself.

The identity element of addition is 0, any number + 0 = any number itself, 0 + any number = any number itself.

These definitions are abstract, so let’s use a piece of JS code to simulate them.

class Monad {

  value = "";

  // constructor

  constructor(value) {

    this.value = value;

  }

  // unit to load the value into the Monad constructor

  unit(value) {

    this.value = value;

  }

  // bind to convert the value to a new Monad

  bind(fn) {

    return fn(this.value);

  }

}



// a function of the x-> M(x) format

function add1(x{

  return new Monad(x + 1);

}

// a function of the x-> M(x) format

function square(x{

  return new Monad(x * x);

}



// Then we can make the chain call

const a = new Monad(2)

     .bind(square)

     .bind(add1);

     / /...



console.log(a.value === 5); // true

Copy the code

The above code is a basic Monad that separates the steps of the program into a linear stream and processes the data stream through bind to get the desired result.

Ok, now that we understand the internal structure of Monad, let’s take a look at the usage scenarios of Monad.

Monad usage scenarios

Many usage scenarios are derived from Monad’s rules.

  • Assemble multiple functions to achieve chain operation.
    • Chain operations eliminate intermediate states and implement the Pointfree style.
    • Chain operations also avoid the nesting problem of multiple levels of functionsfn1(fn2(fn3())).
    • If you’ve ever used RXJS, you know the joy of chain operations.
  • Deal with side effects.
    • Wrap side effects, such as asynchronous IO, in the last step.

Remember ajax from the Jquery era?

$.ajax({

  type"get".

  url"request1".

  successfunction (response1{

    $.ajax({

      type"get".

      url"request2".

      successfunction (response2{

        $.ajax({

          type"get".

          url"request3".

          successfunction (response3{

            console.log(response3); // Get the final result

          },

        });

      },

    });

  },

});

Copy the code

In the above code, we performed three Ajax operations in sequence through the callback function, but also generated three layers of nested code that was not only difficult to read, but also difficult to maintain in the future.

Promise solved these problems.

fetch("request1")

  .then((response1) = > {

    return fetch("request2");

  })

  .then((response2) = > {

    return fetch("request3");

  })

  .then((response3) = > {

    console.log(response3); // Get the final result

  });

Copy the code

We use Promise to encapsulate multiple steps into multiple THEN methods to execute, which not only eliminates the problem of multi-layer code nesting, but also makes the code division more natural, greatly improving the maintainability of the code.

Think about it, why do promises keep executing the then method?

In fact, Promise is similar to Monad in that it satisfies several Monad rules.

  1. Promise itself is a constructor.
  2. The unit in Monad, in Promise, can be read as:x => Promise.resolve(x)
  3. Bind in Monad, as in Promise:Promise.prototype.then

Let’s verify that with code.

// Define two asynchronous handlers first.



// Delay for 1s and then increment by 1

function delayAdd1(x{

  return new Promise((resolve) = > {

    setTimeout((a)= > {

      resolve(x + 1);

    });

  }, 1000);

}



// Delay for 1s and then square

function delaySquare(x{

  return new Promise((resolve) = > {

    setTimeout((a)= > {

      resolve(x * x);

    });

  }, 1000);

}

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



E *a = a*e = a

const promiseA = Promise.resolve(2).then(delayAdd1);

const promiseB = delayAdd1(2);

// promiseA === promiseB, so promise satisfies the left unit element.



const promiseC = Promise.resolve(2);

const promiseD = a.then(Promise.resolve);

// promiseC === promiseD



// Promise satisfies both left and right unit elements, so promise satisfies unit elements.

// ps: However, some special cases do not meet this definition, as discussed below



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



// associative rule :(a * b) * c = a * (b * c)

const promiseE = Promise.resolve(2);

const promiseF = promiseE.then(delayAdd1).then(delaySquare);

const promiseG = promiseE.then(function (x{

  return delayAdd1(x).then(g);

});



// promiseF === promiseG, so Promise is associative.

// ps: However, some special cases do not meet this definition, as discussed below

Copy the code

After looking at the above code, I can’t help but feel surprised that Promise and Monad are too similar, not only can realize the chain operation, but also satisfy the unit element and associative law, is Promise a Monad?

Actually, Promise didn’t quite satisfy Monad:

  • Promise. Resolve If a Promise object is passed in, it waits for the incoming Promise to execute and uses the execution result as the value of the outer Promise.
  • Promise.resolve does not return the thenable object directly, but waits on the then method as a Promise and as the value of the outer Promise.

If these are the two cases, Monad rules cannot be satisfied.

// promise. resolve passes a Promise object

const functionA = function (p{

  P === 1

  return p.then((n) = > n * 2);

};

const promiseA = Promise.resolve(1);

Promise.resolve(promiseA).then(functionA);

// RejectedPromise TypeError: p.then is not a function

// An error was reported because promise.resolve processed the incoming Promise. It violates the identity element and the associative property.



// promise. resolve passes a thenable object

const functionB = function (p{

  P === 1

  alert(p);

  return p.then((n) = > n * 2);

};

const obj = {

  then(r) {

    r(1);

  },

};

const promiseB = Promise.resolve(obj);

Promise.resolve(promiseB).then(functionB);

// RejectedPromise TypeError: p.then is not a function

// An error was reported because promise.resolve handled thenable passed in. It violates the identity element and the associative property.

Copy the code

See here, I believe that we have a new layer of understanding of Promise, it is with the help of Monad like the chain operation, so that Promise is widely used in the front-end asynchronous code, do you also like me, full of good feelings for Monad?

Monad deals with side effects

Next, let’s look at a common question: Why is Monad good for dealing with side effects?

Ps: By side effects, we mean actions that violate the principle of pure functions and should be avoided as much as possible, or done last.

Such as:

var fs = require("fs");



// Pure function, pass filename, return Monad object

var readFile = function (filename{

  // Side effect function: read file

  const readFileFn = (a)= > {

    return fs.readFileSync(filename, "utf-8");

  };

  return new Monad(readFileFn);

};



// Pure function, passing in x, returns Monad object

var print = function (x{

  // Side effect function: Prints logs

  const logFn = (a)= > {

    console.log(x);

    return x;

  };

  return new Monad(logFn);

};



// Pure function, passing in x, returns Monad object

var tail = function (x{

  // Side effect function: returns the last row of data

  const tailFn = (a)= > {

    return x[x.length - 1];

  };

  return new Monad(tailFn);

};



// chain operation file

const monad = readFile("./xxx.txt").bind(tail).bind(print);

// Up to this point, the whole operation is pure, because the side effect function is always wrapped in Monad and is not executed

monad.value(); // Execute the side effect function

Copy the code

In the above code, we encapsulate the side effects function into Monad to ensure the excellent characteristics of pure functions and cleverly resolve the security risks of side effects.

Ok, so far, the main content of this article has been shared, but in the study of Monad one day, suddenly found that someone explained Monad clearly in a sentence, sigh, it is too powerful, let’s have a look!

Warning: If you are not interested in this topic, skip it.

Monad explains in one sentence

More than 10 years ago, Philip Wadler summed up Monad in one sentence.

译 文 : A monad is A monoid in the category of endofunctors.

Monad is a monoid on the category of self-functors.

There are three important concepts labeled here: self-functors, categories, and monids. These are all mathematical knowledge, and we will consider them separately.

  • What is a category?

Everything is an object, and a large number of objects combine to form a set. There is one or more connections between objects, and any connection is called morphism.

An algebraic structure of a collection of objects and all morphisms between them is called a category.

  • What is a functor?

We call the mappings between categories functors. A map is a special morphism, so a functor is also a morphism.

  • What is a functor?

A self functor is a functor that maps a category to itself.

  • What is a Monoid?

Monoid is a semigroup with identity element.

  • What is a semigroup?

If a set satisfies the associative property, then it’s a semigroup.

  • What is the unit element?

An identity element is a special element of a set, associated with binary operations in that set. When units are combined with other elements, they do not change those elements.

Any number + 0 = the number itself. So 0 is the identity element (the adding identity element) any number times 1 is equal to the number itself. So 1 is the identity element.Copy the code

Ok, now that we know all the technical terms you need to know, let’s just go through this explanation:

A monoid on a category of self-functors can be understood as, in a set that satisfies the associative and identity element rules, there exists a mapping that maps the elements of the set to the elements of the current set.

I believe that after mastering these theoretical knowledge, I will certainly have a deeper understanding of Monad.

conclusion

Starting from Monad wikipedia, this paper introduces the internal structure and implementation principle of Monad step by step, and verifies the important role of Monad in actual combat through Promise.

The article contains a lot of mathematical definition, functional programming theory and other knowledge, mostly refer to network information and self experience, if there is a wrong place, but also hope you give directions 🙏

Finally, welcome to pay attention to the public number: [front-end log], share front-end knowledge!