DEV Community

artydev
artydev

Posted on

Discovering the notion of Monad in Javascript

You already know what Monads are about, so I won't repeat what you can read about them in the numerous articles on the net.

I am taking the point of vue of someone completely novice who wants to apprehend this notion without any mathematical concepts.

For me, all starts with the notion of closure.

A closure is a feature of JavaScript that allows inner functions to access the outer scope of a function.

Ok, let's write our first closure :

const adder = (m) => (n) => m + n;
const add2 = adder(2);
console.log(add2(5)); // 7
Enter fullscreen mode Exit fullscreen mode

Yes, it's a little magic, but that is the way it works.

So, the question is what if we want to generalise the function to apply to a given value (here 5) ?

In order to simplify things, the function to apply will take a single argument.

So we could write :

const addTwo = x => x + 2
const transform = (value) => (fn) => fn(value)
console.log(transform(5)(addTwo)) //7
Enter fullscreen mode Exit fullscreen mode

With that in place, we can pass whatever function to our transform
function, nice.

Now, let the expression transform(5)(addTwo) be the result of a function called PeudoMonad and let's rename transform by map and switch the arguments, we get then :

const PseudoMonad = (value) => ({
   map (fn) {
    return fn(value)
  }
})
console.log(PseudoMonad(5).map(add2)) // 7
Enter fullscreen mode Exit fullscreen mode

What if we could, carry the context of our PseudoMonad, ie carry the initial value ?

Let's rewrite our PseudoMonad :

const PseudoMonad = (value) => ({
   value,
   map (fn) {
    return fn(this.value)
  }
})
console.log(PseudoMonad(5)) // { value: 5, map: [Function: map] }
Enter fullscreen mode Exit fullscreen mode

Great, along our computation, with get the initial value, which is unchanged

So what do we get so far, an object which remember of it's initial state and a method which operates transformations on this state.

A question arise, how can we chain these transformations ?
Actually the return type of our map is a number.

Instead of returning a value of type number from our map function, what if we return an object of type PseudoMonad

Let's try :

const PseudoMonad = (value) => ({
   value,
   map (fn) {
    return PseudoMonad(fn(this.value))
  }
})
Enter fullscreen mode Exit fullscreen mode

The map method returns now an object of type PseudoMonad

Let's see if that works :

const result =  PseudoMonad(5)
    .map(x => x + 3)
    .map(x => x * 100) // 7

console.log(result)
Enter fullscreen mode Exit fullscreen mode

Result :

{ value: 800, map: [Function: map] }
Enter fullscreen mode Exit fullscreen mode

Great, we can easily chain transformations to our PeudoMonad.

If you wonder what is the deal with this, look at the following
example :



function validUser (user) {
  return user.age >= 18
}

const PseudoMonad = (value) => ({
   value,
   map (fn) {
    if (!validUser(this.value)) {
       return PseudoMonad(null)
    }
    else {
       return PseudoMonad(fn(this.value))
    }
  }
});

const Hal = PseudoMonad({
  name : "Hal",
  age : 3,
  numbadge : -1
});

const John = PseudoMonad({
  name : "John",
  age : 34,
  numbadge : -1
});

const Alice = PseudoMonad({
  name : "Alice",
  age : 24,
  numbadge : -1
});

function getBadge() {
  let  numberbadge = 0
  function _getBadge (user) {
    numberbadge += 1
    user.numbadge = numberbadge
    return user
  }
  return _getBadge
}


const attendees = [Hal, John, Alice]
  .map(u => u.map(getBadge()))
  .filter(u => u.value)

console.log(attendees.map(v => v.value.name))

Enter fullscreen mode Exit fullscreen mode

The getBadge function is always passed a valid user, and the same for the eventually chained functions.

Hal is not passed to getBadge. (getBadge() here)

The callers don't have to do any checkings and so, they are easlily testables, that's the big deal.

Betwise, the PseudoMonad described here, is an approximation of what is a called a Maybe monad.

Demo

Top comments (12)

Collapse
 
efpage profile image
Eckehard

But what is the advantage? Is there any real use where building functions like Matryoshka dolls really makes things easier?

Collapse
 
artydev profile image
artydev • Edited

:-D,
Your questions are always pertinents.

Look at the last example how the getBadge function does not have to deal if a user is valid or not, it's always passed a valid user.

With that "mecanics", chained function are not called if the value does not respond to some criterias

Demo

Collapse
 
efpage profile image
Eckehard

I cannot even think how this could be easy...

attendees is not an array of objects, but an array of PseudoNomads. I cannot even imagine, if the code below is ok or not, as I do not know, what an array of Pseudonomads is or what Pseudonomad() returns. I first have to understand how this was defined to check the whole code.:

[Hal, John, Alice]
  .map(u => u.map(getBadge()))
  .filter(u => u.value)
Enter fullscreen mode Exit fullscreen mode

The second line look suspicious, as it would execute Hal.map(getBadge), but Hal is not an array, but a pseudonomad. But ok, getBadge() is a function that returns a function, why not...

So, here is the explanation of the nested arrow functions you use, which is not easy to understand:

const transform = (value) => (fn) => fn(value)
transform(5)(addTwo)
Enter fullscreen mode Exit fullscreen mode

Oh, how nice, A function with two argument brackets. Is this valid Javascript?
Can we remove the round brackets on value or fn, as is allowed for arrow functions with one argument? What value will numberbadge have, as this is a local variable? Hard to say...

Assume, we find this code somewhere in the wild? Even with your explanation by hand I´m struggeling to understand the code I see. What precisely is this?

({value, map (fn) {return fn(this.value)}})
Enter fullscreen mode Exit fullscreen mode

not a valid Javascript expression that I have ever seen. You will hardly findy any help for this, as map is explained to be an array function, not anything you could use alone. But I even could not tell if this could be an object or not.

Pseudonomad() looks like an arrow function that returns an object embraced in round brackets. If you do not know this exact and rare construction, it is virtually impossible to understand the code. Unreadable code is the small sister of unmaintainable code and a good reason to bury a whole project after some time.

And what is about debugging? Do we have to go the whole way down to the core of the Matryoshka to find any errors?

I´m not sure that this programmatical shell games could really be appealing.

Thread Thread
 
artydev profile image
artydev • Edited

Eckehard,
I must first apologize to have made such a poor presentation of the concept of Monads.
But they are really not that easy to explain.

The followed example should have gave you a better understanding of why they are usefull.
Indeed we are assured to never have to deal with "null reference object" and that is very nice.

I really enjoin you to document yourself about functional programming in JS, and what are Monads and their usefullness.

Here is a presentation : Functional Programming with JavaScript

Those concepts are largely olders than Object Oriented Programming.

Javascript is a language mainly based on Scheme which is a functional programming language rather than Java.

In FP, functions are not differents than any other variables, they are first class citizens.
They can be assigned to a variable, pass as arguments to other functions.
From an O.O programmer, FP is a real shift in mind.

POO is rarely used as it should be, and in large programs it can be very complex to follow the thread of execution because of inheritance, aggregation, implementation , abstract and virtual classes, privacy, sealed, static and so on, too much concepts for me.

In FP you simply let the flow of data running through composed transformations, for me it is easier to follow.

Look why POO can sometimes pose problems by looking at this code from : FP

What is the real outcome of result ? Is it that easy to solve it ?
and this is a very simple program.

class Flock {
  constructor(n) {
    this.seagulls = n;
  }

  conjoin(other) {
    this.seagulls += other.seagulls;
    return this;
  }

  breed(other) {
    this.seagulls = this.seagulls * other.seagulls;
    return this;
  }
}

const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
  .conjoin(flockC)
  .breed(flockB)
  .conjoin(flockA.breed(flockB))
  .seagulls;
Enter fullscreen mode Exit fullscreen mode

FP is a paradigm not easy to apprehend, but a nice tools in your toolbox nothing less nothing more.

Thread Thread
 
efpage profile image
Eckehard • Edited

Thank you much for explaining backgrounds. Im really struggeling to get a clue on all this concepts. If I learn something that could make my life easier, i would really appreciate to adopt this. It does not matter how old a concept is, as long as it is useful.

Speaking of OOP you will find different concepts in history, initial ideas have been developed in 1966, so it will not have a lack in maturity. But there are different concepts like objects that pass messages only (smalltalk) or objects as a unity of procedures and data (C++ or Object Pascal). None of this concepts realy fits perfectly into the Javascript world, as JS lacks some core concepts. But there are cases where using structures of OOP is most useful.

I assume, this is very similar with Functional Programming, and there are situations where passing functions as a parameter can be most helpful. But as with any concept there should be a good reason to use it. If you force people to use objects and classes where simple data would do a better job, they will probably not get a clue on the benefits of OOP. This is a very similar situation here, where we build a shell game of hidden relations that are more confusing than helpful.

There are some basic rules that we should keep in mind:

  • Keep it simple
  • Don´t repeat yourself

If it´s hard to explain and the resulting code is hard to read, hard to understand and hard to debug, it is possibly not as simple as it should be.

I know that this was only an example to show the principles, but if relations and dataflows in a simple example are that confusing, how should we get a larger codebase up and running?

Thread Thread
 
artydev profile image
artydev • Edited

Haskell is not particularly easy to grasp but it is very used in financial companies.
I can't event imagine OOP projects contolling hypermulthreadings software running on multicores CPU.
Again every paradigm as it's use and I can assure you this is the case for Functional Programming.

In FP, it's not the really the usage that is difficult to apprehend, because it is very declarative, but the theories behind it.

Thread Thread
 
efpage profile image
Eckehard

Ok, are you really building this kind of software? With javascript? I assume, most people will be happy to write a better version of paint without too many crashes...

Thread Thread
 
artydev profile image
artydev • Edited

:-) No, of course not.
It was just an illustration that functional programming is actually use in large applications.
In fact the two paradigms can coexist in any application.
If you want another illustration, React is based on functional programming model.

In my use case I use functional style very often in C# using what's called 'linq'.
In Java the same features are implemented in 'streams'.
And C# and Java are two Object Oriented language.

I have presented the Maybe monad in C# to my colleagues,, but they prefer dealing with the Null Reference Type which is a new feature in C#, juste a matter of taste

You can look this video to see how web apps can be constructed using FP : WenApp in FP

Thread Thread
 
efpage profile image
Eckehard • Edited

Would you put slicks on your fiat chiquito, just because they are used in the formula one?

I´m pretty sure React was the last choice that a financial company would make to contoll their hypermulthreadings software running on multicores CPUs. I´m even unsure if react follows any paradigm at all :-).

Initially they used classes to inherit the core functionallity to react components, so they had some OO elements too. They use some principles of FP, but can you tell, which paradigm HOOKS follow? Beside the fact that many people find them better than the initial methods React provided, they often seem more like a dirty hack than any well designed paradigm.

Even if FP plays well for financial applications that are written by large teams of mathematicians, do we get any benefit for web programming? The DOM is stateful, so adopting principles of FP to write DOM applications will always force you to add things like a Virtual Dom to get your work done. To me this looks like trying to make a circle square.

Thread Thread
 
artydev profile image
artydev • Edited

Hy Eckehard :-)
I would say FP benefits for "javascript" programming
Give an eye to ELM, ReScript, PureScript, DERW just to name a few and see what they give to 'web application programming'

Eckehard, I will not and don't want to convert you, but If you really want to see the benefits on FP,
please read from people that promote and master it.

Among them there is James Sinclair WebDev/FP

Here is what he says in one of his article :

Lets' be honest. Nobody cares what functional programming is, at least, not at first. What we care about is “can we deliver better code, faster?”

Functional programming is all about having confidence in our code. It’s about knowing that our code is doing what we expect, and nothing but what we expect.

Thread Thread
 
efpage profile image
Eckehard

I read some articles of James Sinclair, whos website is really beautiful. And he adresses the questions directly, so thank you much for the link.

From the theoretical point, I can really understand a lot of this arguments, But from a practial view, I´m not sure they really adress the important topics. I´m even not sure if FP and OOP address the same topics at all.

There have been different ideas behind OOP, but the way is was implemented in C++ or Delphi is just a layer ontop of a procedureal language. OOP is a way to build smaller, encapsulated code units with a well defined interface. At the core, any object is a bit like a procedural program on it´s own.

Why should we store the color of a button in a StateMonad? Does this make any sense? It is just a color and the variable has just the task to store this color. OOP has all the tools to care, that a change of a property is reflected in the UI, so why should we use anything else? It does not make our code any better. It is a simple task, and we need a simple tool for it. If OOP provides this in a most conventient and secure way, why do anything else? Most task in programs are that simple.

But there might be situations, where we need to deal with complex mutations. Why not build our objects using the principles of functional programming where applicable? Even with James Sinclair´s explanation I struggle to get the concepts, but I assume, there will be a useful core. So, why not build "Functional Objects"?

I did not find any good answer to this questions. I just see, that many large projects like Google Chrome are still written in C++, even though the principles of FP are known for a very long time.

Thread Thread
 
artydev profile image
artydev

I really thank you to have taken time to read those articles.
I understand better your arguments now.

In our day to day work, FP and OO can collaborate and are not antagonist, in any way.

The idea of "Functional Objects" will perhaps arise, who knows...