TOP

Monads - In A World of Multipolarism

Aggregated "Super Mario"?

About a decade ago, I found a repo in Github for a tiny Super Mario program written in JS. Interestingly, the program was not designed with OOP.
I still can't find the original repo, but it looked like this:
var mario = Entity.compose(jump, run)
var turtle = Entity.compose(slide, bounce, jump, run)
If it were in OOP, we would first define a base class. Then, define 'Mario' and 'Turtle' by extending the base, and implement shared methods so that they would behave alike.
Also, it was using a strange function 'compose' which seemed to aggregate desired behaviors. I further noticed that the 'compose' function takes arguments from right-to-left. To create 'turtle' instance, it takes 'run', 'jump', 'bounce', and 'slide' accordingly.
Using 'class' syntax of ES6 would look like:
class Mario {
  constructor() { this.speed = 100 }
  run() {}
  jump() {}
}
which translates to:
function Mario() { this.speed = 100 }
Mario.prototype.jump = function() {}
Mario.prototype.run = function() {}

Crockford's 'Private'

You probably know the book "Javascript:The Good Parts" which was written by Douglas Crockford in 2008.
There, he introduced the famous "classless" module pattern, and it somewhat looks like this:
// This is heavily modified from Crockford's original...

const Jumper = () => ({ jump: () => {} })
const Slider = () => ({ slide: () => {} })

const Mario = () => {
  const speed = 100
  const jump = () => { Jumper().jump() }
  return { jump }
}

const Turtle = () => {
  const speed = 100
  const slide = () => { Slider().slide() }
  return { slide }
}
Notice how 'jump' and 'slide' are exposed as public while 'speed' being private. This is the Crockford's way of encapsulating data.
(we'll come back to "encapsulation" later because it is the main theme for today)
Actually, this pattern has been known as "Revealing Module Pattern". Yet, people hated it because it breaks inheritance. But, that's exactly the point; After all, we don't need "inheritance".

You Must Be A Very Good Designer

There is an interesting article about James Gosling (an inventor of Java) once told the audience:
(bellow is actually Allen Holub, a famous software architect, speaking in his blog)
https://www.infoworld.com/article/2073649/why-extends-is-evil.html
I once attended a Java user group meeting where James Gosling was the featured speaker. During the memorable Q&A session, someone asked him:
"If you could do Java over again, what would you change?"
"I'd leave out classes," he replied.
After the laughter died down, he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.
The major problem with inheritance in OOP is that you must be pretty good at designing relationships. If a system was not perfectly designed, it can break. And, when it does, you are spending another day figuring out why... and it is really a painful process.

Rust Ditched 'class' and 'inheritance'

The idea of "inheritance" is very strong, but it can often bring you headaches than benefits. For this reason, Rust team made a design decision not to implement 'class' nor 'inheritance'. Instead, Rust has 'trait'. This is "composition" over "inheritance".
Let's see how Rust implements 'trait':
trait Jumper {
  fn jump() {}
}

trait Slider {
  fn slide() {}
}

struct Mario { speed: f32 }
struct Turtle { speed: f32 }

// See 'Mario' and 'Turtle' both bear 'Jumper' trait.

impl Jumper for Mario {}

impl Jumper for Turtle {}
impl Slider for Turtle {}

// Now, let's create instances!

let mario = Mario { speed: 100 }
let turtle = Turtle { speed: 90 }

// See how it can iterate different types.
// (but for ones that bear 'Jumper' trait)

let v: Vec<&Jumper> = [ &mario, &turtle ]

for entity in v.iter() { render( entity ) }

fn render<T: Jumper>(entity: &T) {
  entity.jump()
}
Can you see?
Without the use of "inheritance", you can still achieve the same with "composition".

Orchestration vs Pipes

This is more about differences in approaches.
In OOP, you emphasize "designs". You design models so that objects would possess specific roles, and you expect the roles to work. In OOP, you must be well aware about your targets. You must design them accordingly, and you must "orchestrate" them perfectly without flaws.
No wonder that an analogy of 'Animal' and 'Dog' is frequently used for explaining OOP. Given a generic notion like 'Animal', you know perfectly well about what 'Dog' is in comparison to 'Animal'.
Let's say, you are unclear about your target. You have no clues what roles to give. Then, you simply fail to properly link one object to anther, and you would end up jeopardizing the entire system.
In FP, you don't pay too much attention to "designs". Instead, you pay attention to "how data is consumed". It does not ask you to perfectly know about relationships among players, but asks you to prepare for the time when data is consumed. It is more like builiding "pipes" so that water flows when it gets there.
So, this part right here:
fn render<T: Jumper>(entity: &T) {
  entity.jump()
}
is probably the highlight in FP practices. In the above function, you pay attention to make sure the argument 'entity' bears the 'Jumper' trait.

Compressed vs Dispersed

Now, think about how little is needed for 'Jumper::jump()' in the above. In OOP, you tend to squeeze as much features as possible into your methods.
In FP, you don't need much. Instead, you just need to make sure that the function always returns the same result. It does not necessarilly has to be complex. Each function plays its own part in FP. When demand arises, you assemble these functions to acommplish more complicated tasks.

Server Apps in the 90s

In the 90s (or in early 00s), MVC was a thing for server-side applications, and we all wrote applications in OOP.
Say, you wanted a controller. You would first write a base class for it. If you wanted more controllers, then you would simply extend the base class. It worked perfectly because features for server-side applications usually didn't have much differences, and you only needed slight variations.
OOP was also effective in preventing apps from becoming a chaos. Since your base class provided a blueprint, as long as other classes followed the base, you had nothing to worry.
OOP also provided a great mental model. Since you are well aware of what you have in your parent class, you only needed to make the sibling classes to behave a bit differently, and it barely required any sophisticated thinking.

Central Governance vs Modularity

Although such has been the standard in main stream programming practices (for which C or C++ derived languages were the major languages) there was another set of ideas in programming world, and that was "Functional Programming (FP)".
In FP systems, everything is "modular". You can easily replace a library with another (even at runtime for some systems). Each module works on its own, and is less depended on others. You may say peripherals have more power.
In OOP systems, you have a master in the center for others to follow. In a sense, you could say that it is driven by the idea of central governance.
It doesn't mean FP system is in anarchy. Not at all. Just like any Non-FP systems, it has a core architecture to control the rest. It is just each module is more independent.
Let's see the FP systems that we are familiar with.

React

React is obviously a successful example in bringing FP practices into front-end world. When Jordan Walke invented React, he wrote its prorotype in a FP language 'SML' (Standard Meta-Language).
(and it was later ported to 'OCaml' which is another FP language)
As you know, everything in React is "modular". What's nice about React is that you have higher abstractions in components. Components are written in declarative way, or they are not bound to specific contexts. Only when contexts were provided, will they behave the way you want. Now, that is very FP.

JavaScript

When Brendan Eich was asked by Netscape to create a new language for browsers in 1995, what he had in mind was Scheme (a dialect of Lisp). Although the board asked him to make its syntax resembling that of Java (or C/C++ derived languages) and Brendan Eich made it so, deep down, JS has always been a FP language after all.
Unfortunately, we're not using JS correctly today. For instance, as Crocford puts it, introduction of 'class' syntax was nothing but a blasphemy...

Looking at "Patterns"

Let's now forget about the world of programming.
Let's talk about our own career as developers.
If we were to aim at a distant future, say, 5 years from now. It would be easy to predict how the technology would evolve. But, foreseeing that of 10 or 20 years from now... Well, you need something other than technology. Instead, you probably need to look into "where humanity is heading".
Is it far fetched?
When we have a specific technology, that's because people wants it. To see why it is so, we need to look for "patterns". And, such "patterns" is found in our society today.
Let's see what I mean.

(a) Multipolarism

So, the 1st pattern is what we call "multipolarism" in politics today.
Until the 90s, the military-industrial complex (the U.S. defence complex along with UK-Israeli allies) was in control for major world events. As the global capitalists gradually began taking over, the U.S. gave up on being the world's police force. Instead, they started supporting sovereignty of nations that were once under the military control, and withdrew from these nations (Ukraine is an exception and is totally another story). They did so, because the U.S. defence spending was skyrocketting. Simply, it was too expensive.
The same has happend to Japanese government. Unlike in the 90s, the center gave up on its power, and gradually began allowing local governments to manage their own. And, it was for the same reason: it was too expensive. Nowadays, they are outsourcing their services more to private sectors.
Here in the realm of politics, we see the pattern: the central authorities giving up on their powers over peripherals. This is "multipolarism".

(b) Dispersity

The 2nd pattern is about "dispersity".
Like, in Blockchain. Blockchain technologies is all about "decentralization". Once we had a master database is now replaced with distributed ledger systems. Or, similar is found in cloud computing for which resources are migrating more to the edge as a way to reduce the overheads.
Or, we can look at ourselves. Today, our life is indeed a life of "solitude". It is especially so in big cities like New York or Tokyo. We can stay in our apartment housings all day long, ordering pizza when we're hungry, and buying commodities from online shops suffices everything we need. Family unit has become smaller. Or, you need absolutely no one for your entire life. No problem. That's a norm for today.
You see, we could say that our lives are shattered in thousand pieces. The power still risides in authorities. However, the central authorities are delegating tasks to individuals (peripherals), and individuals (peripherals) are the ones carrying out the many tasks today. Peripherals are becoming their own CPUs.
---
Let's come back to the world of software development.
Now, we can see the patterns that we observed in our society.
Instead of the master controlling everything, softwares are becoming more peripheral oriented.
Components are becoming more modularized, each running independent of the master.

Re-Visiting Crockford's

Now, we are back to the world of programming.
In React, we have "higher-order functions" (e.g. Context Providers, Selectors/Reducers, Components, etc...) Like I already stated before, the idea is to write programs in highly abstract manner so that contexts are fed later when they are needed.
Let's, again, look into Crockford's example:
const Jumper = () => ({ jump: () => {} })
const Slider = () => ({ slide: () => {} })

const Mario = () => {
  const speed = 100
  const jump = () => { Jumper().jump() }
  return { jump }
}

const Turtle = () => {
  const speed = 100
  const slide = () => { Slider().slide() }
  return { slide }
}
Like already mentioned 'speed' is being a private. In a way, 'speed' is trapped in the function scope. Or, you may say "wrapped" or "encapsulated" within the scope.
So, it looks like this:
function x() {
  private

  return {
    public
  }
}

Monads

When a set of data is "wrapped" or "encapsulated" in a function, it is called a 'Monad' in functional programming terminologies. The benefit of 'Monad' is that you can contain side effects within itself.
Here is a working example:
const Result = () => {
  let _val
  let _err

  const ok = value => {
    _val = value
  }
  const err = error => {
    _err = error
  }
  const isOk = () => !_err
  const unwrap = () => _val
  const unwrap_err = () => _err

  return { ok, err, isOk, unwrap, unwrap_err }
}

Result / Either / Optional

There are many types of 'Monad' in functional programming. The above is one of the variants, and it is called, 'Either' or 'Optional'.
In Rust, it has 'Result' type.
(Rust also has 'Option' type along with 'Result' type)
Using 'Result' above:
const get_entity = async () => {
  const res = Result()
  try {
    const entity = await Api.get_entity()
    res.ok(entity)
  } catch (err) {
    res.err(err)
  }
  return res
}

const mario = await get_entity()

mario.isOk()
  ? Cookie.set('mario', mario.unwrap())
  : console.error( mario.unwrap_err() )
Notice we have 'try/catch' in 'get_entity', and we can safely assume the result is always either 'Ok' or 'Err' (that's why it is called 'Either' monad).
Since you know what comes out, you can "pipe" the result.

Why Do We Need Monads?

Why is it that we want Monads so bad? Let us look at compose from the Super Mario example:
var mario = Entity.compose(jump, run)
var turtle = Entity.compose(slide, bounce, jump, run)
Now, consider what you want RETURNED from 'run', 'jump', 'bounce', and 'slide'?
1. We do NOT want "inheritance".
2. Instead, we want "composition".
3. To have "composition", we need:
a. All side-effects to be contained within a function.
b. The function to always return the same result.
4. We want Monads.

Promise

If you noticed, 'Promise' in JS, in a way, is a 'Monad' as well.
get_entity()
  .then(mario => {
    Cookie.set('mario', mario)
  })
  .catch(err => {
    console.error(err)
  })
Depends on what you get, you can "pipe" the result to the next.

What to Consume?

In any case, it is essential that we have an assurance that a function to always return the same.
Let's see how the same is done in Rust:
async fn get_entity<T: Jumper> -> Result<T, String> {
  Api.get_entity::<T>()
}

match get_entity::<Jumper>().await {
  Ok(entity) => Cookie.set(entity)
  Err(err) => log_err(err)
}
It is fairly the same in Rust. However, notice how 'get_entity' differs. For the above Rust code, 'get_entity' only takes an argument which implements 'Jumper' trait. Meaning, 'get_entity' takes either 'Mario' or 'Turtle'.
So, this is a good example that we focus on the time of "consumption" rather than on "designs".

Conclusion

OOP asks you being a "perfect designer".
You need to perfectly be aware of relationships among players.
Whereas in FP, you focus when functions are consumed.
It does not ask you to perfectly link the players, but to focus on returning the same results.
Think of the world you have a master in the center, and you always have to clone copy for desired behaviors.
Versus the world in which every component plays its own role.
Which one do you think the system be simpler?

References

TOP