Skip to content

Instantly share code, notes, and snippets.

@jbreckmckye
Last active April 18, 2016 21:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbreckmckye/aef784682756add5ac464d248b40545a to your computer and use it in GitHub Desktop.
Save jbreckmckye/aef784682756add5ac464d248b40545a to your computer and use it in GitHub Desktop.
Old monad tutorial
layout title date comments categories
post
The gist of monads
2016-04-07 00:47:16 +0100
true
JavaScript
functional programming

Most monad tutorials are long, confusing, and ineffective. I cannot promise this one will be more clear, more interesting, or even more effective, but I can at least promise to make it brisk. You have nothing to lose by reading it.

Take a look at this object, Foo. It's a monad, but what that means isn't relevant right now. Let's just focus on how Foo specifically works:

function Foo(value) {
    this.get = ()=> value;
    this.bind = fn => {
        let result = fn(value);
        return new Foo(result);
    };
}

Foo holds a single value that never changes. It provides a method for getting it, and a curious method named bind. Bind gets a function, passes it the value, and then returns the result in a new Foo.

Because bind returns a new Foo, it's chainable:

let one = new Foo(1);
let two = one.bind(x => x + 7).bind(x => x / 2).bind(x => x - 2);
two.get() === 2;

This chaining is kinda neat - it lets me write the operations I want to perform on x in order, rather than with nested function calls, like below:

let two = minusTwo(divideByTwo(addSeven(1))); // I have to read this right-to-left, which is awkward

But the real value to an object like Foo is that if I put logic inside bind, I can abstract away an action that I want to perform between each step of an operation.

Consider another monad, named Bar:

function Bar(value) {
	this.get = ()=> value;
	this.bind = fn => {
		let result = fn(value);
		console.log(result);
		return new Bar(result);
	};
}

If I have an operation that makes multiple changes to a value, and I want to log how that value mutates at each turn, Bar lets me swap code like this...

let stepOne = something(1);
console.log(stepOne);
let stepTwo = somethingElse(stepOne);
console.log(stepTwo);
let stepThree = somethingDifferent(stepTwo);
console.log(stepThree);

...for code like this:

new Bar(1)
	.bind(something)			// console >> logs new value
	.bind(somethingElse)		// console >> logs new value
	.bind(somethingDifferent);	// console >> logs new value

You now understand monads. I did promise this would be quick. Monads can be boiled down - roughly - to the following rules:

  1. A monad contains a value
  2. A monad has a method named bind that takes a function
  3. Bind has some logic around calling that function and processing the result
  4. Bind then returns the result in a new monad, so that calls to bind can be chained

That's it. If you understand how the above code works, you understand monads. Sorry if you were hoping for something more magical and mind-bending.

We can do anything we like in bind. Anything that we want to do between the steps of an operation - be that deciding how we pass the value into the next step, or doing something with the result that comes back out - we can put inside bind and abstract away. Null checking is a great example:

function Maybe(value) {
	this.get = ()=> value;
	this.bind = fn => {
		if (value === null) {
			return new Baz(null);
		} else {
			return new Baz(fn(value));
		}
	};
}

In Maybe, bind only calls the supplied function if it can hand it a valid, non-null value. Otherwise it quietly returns a clone of itself. This lets us turn verbose, repetitive code like this...

let connection = getConnection();
let user = connection ? connection.getUser() : null;
let address = user ? user.getAddress() : null;
let zipCode = address ? address.getZip() : null;

...into a much more elegant alternative:

let zipCode = 
    new Maybe(getConnection())
    .bind(c => c.getUser())
    .bind(u => u.getAddress())
    .bind(a => a.getZip);

zipCode.get(); // returns either a zip code (if every step worked) or a null (if any step returned one)

Hopefully these two examples are enough to show you why monads and their bind methods are so powerful. No doubt you can imagine uses of your own.

There are scores of other monads, all with quite diverse uses and functionality. But they all follow those same four rules we saw in Foo and Bar.

@denilsonsa
Copy link

I mentioned this in the Disqus comments: Shouldn't Maybe.map return a new Maybe() instead of a new Baz()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment