After reading dozens of “most understandable introductions to monads” and reading (also) dozens of discussions in various forums, I came to the conclusion that there is a group of abstract OO programmers to whom my interpretation of “something similar to monads” can help to get a little closer to the correct understanding.
So, in this publication you will not find answers to the following questions:
1. What is a monad?
2. Where and how to use monads?
3. Why are monads better than none?
In programming there is such a phenomenon - “design patterns”. Officially, this is a set of best practices that should guide when solving "typical problems". Unofficially - just a set of crutches for languages in which there are no built-in tools for solving typical problems.
There is such a design pattern -
Interpreter . It is remarkable first of all because it allows you to make a kind of virtual machine on top of your favorite programming language, with:
')
1. You can describe the program in a language understandable to the virtual machine.
2. It is possible
to rob the cows in all details to describe how the virtual machine should interpret each instruction.
All that is written below makes sense only if the amiable reader is minimally familiar with the pattern mentioned.
Relatively canonical example:function add(x) { return { op: "add", x: x }; } function div(x) { return { op: "div", x: x }; } function run(value, statements) { for(var i = 0; i < statements.length; ++i) { var statement = statements[i]; var op = statement.op; var x = statement.x; if(op === "add") { value += x; } else if(op === "div") { value /= x; } else { throw new Error("Unknown operation " + op); } } return value; } var program = [ add(10), div(3) ]; var result = run(0, program); console.log(result);
GoF lovers can argue, they say, "this is a Command, not an Interpreter." For them, let it be Command. In the context of the article is not very important.In this example, first, there is a program consisting of two instructions: “add 10” and “divide by 3”. Whatever that means. Secondly, there is a performer who does something
meaningful looking at the program. It is important to note that the “program” influences the outcome of its performance very indirectly: the performer is absolutely not obliged to follow the instructions top-down, he is not obliged to execute each instruction exactly 1 time, he generally can
add () calls to
Hello , and
() - in the
"World" .
We agree that the translation of
add () in
console.log () is not interesting for us. Interesting calculations. Therefore, we simplify the code a bit by discarding unnecessary flexibility:
function add(x) {
It is worth staying here. We have a certain tool that allows us to separately describe the program and separately the “method of its execution”. Depending on our wishes for the performance result, the implementation of the contractor can be very different.
For example, it would be desirable that as soon as somewhere in the calculations appears
NaN ,
null or
undefined , the calculations are stopped and the result is returned
null :
... function run(value, statements) { if(!value) { return null; } for(var i = 0; i < statements.length; ++i) { var statement = statements[i]; value = statement(value); if(!value) { return null; } } return value; } console.log(run(undefined, [add(1)]));
Good. What if we want to run the same program for a collection of different initial values? Also not a question:
... function run(values, statements) { return values.map(function(value) { for(var i = 0; i < statements.length; ++i) { var statement = statements[i]; value = statement(value); } return value; }); } var program = [ add(10), div(3) ]; console.log(run([0, 1, 2], program));
Here it is worth staying again. We use the same expressions to describe a program, but depending on the performer, we get very different results. Now let's try to rewrite the example a little. This time, first, we remove a little more flexibility: expressions are now performed strictly from the first to the last, and second, we will get rid of the loop inside
run () . The result is called the word
Context (so that no one guessed):
... function Context(value) { this.value = value; } Context.prototype.run = function(f) { var result = f(this.value); return new Context(result); }; var result = new Context(0) .run(add(10)) .run(div(3)) .value; console.log(result);
The implementation is very different from the previous options, but it does roughly the same thing. Here it is proposed to introduce the term munada (from the English.
Moonad - "moon advertising"). Hello, Identity moonad:
... function IdentityMoonad(value) { this.value = value; } IdentityMoonad.prototype.bbind = function(f) { var result = f(this.value); return new IdentityMoonad(result); }; var result = new IdentityMoonad(0) .bbind(add(10)) .bbind(div(3)) .value; console.log(result);
This thing is a bit like the
Identity monad .
Let us now recall the version of the performer, where we fought
NaN and try to rewrite it using a new approach to implementation:
function MaybeMoonad(value) { this.value = value; } MaybeMoonad.prototype.bbind = function(f) { if(!this.value) { return this; } var result = f(this.value); return new MaybeMoonad(result); }; var result = new MaybeMoonad(0) .bbind(add(10)) .bbind(add(undefined)) .bbind(div(3)) .value; console.log(result);
You can even more familiar example:
var person = {
From a distance it may seem like this
Maybe monad . The amiable reader is invited to independently implement something similar to the
List monad .
With basic file skills, it will not change the
IdentityMoonad so that the
f () calls become asynchronous. The result is a
Promise moonad (something like
q ).
Now, if you look closely at the latest examples, you can try to give a more or less formal definition of a munada. Munada is a thing that has 2 operations:
1. return - takes the usual value, puts it in a munadic context and returns this same context. This is just a constructor call.
2. bind - takes a function from a normal value that returns a normal value, executes it in the context of a munadic context, and returns a monadic context. This is a call to `bbind ()`.